这节写下预定管理模块管理端的前端部分:
对应的路由我们前面写过了:
这节来填充下内容。
首先是 table 部分:
import { Badge, Button, DatePicker, Form, Input, Popconfirm, Table, TimePicker, message } from "antd";
import { useEffect, useState } from "react";
import { ColumnsType } from "antd/es/table";
import { UserSearchResult } from "../UserManage/UserManage";
import { MeetingRoomSearchResult } from "../MeetingRoomManage/MeetingRoomManage";
import dayjs from "dayjs";
interface BookingSearchResult {
id: number;
startTime: string;
endTime: string;
status: string;
note: string;
createTime: string;
updateTime: string;
user: UserSearchResult,
room: MeetingRoomSearchResult
}
export function BookingManage() {
const [pageNo, setPageNo] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [bookingSearchResult] = useState<Array<BookingSearchResult>>([]);
const columns: ColumnsType<BookingSearchResult> = [
{
title: '会议室名称',
dataIndex: 'room',
render(_, record) {
return record.room.name
}
},
{
title: '会议室位置',
dataIndex: 'room',
render(_, record) {
return record.room.location
}
},
{
title: '预定人',
dataIndex: 'user',
render(_, record) {
return record.user.username
}
},
{
title: '开始时间',
dataIndex: 'startTime',
render(_, record) {
return dayjs(new Date(record.startTime)).format('YYYY-MM-DD HH:mm:ss')
}
},
{
title: '结束时间',
dataIndex: 'endTime',
render(_, record) {
return dayjs(new Date(record.endTime)).format('YYYY-MM-DD HH:mm:ss')
}
},
{
title: '审批状态',
dataIndex: 'status'
},
{
title: '预定时间',
dataIndex: 'createTime',
render(_, record) {
return dayjs(new Date(record.createTime)).format('YYYY-MM-DD hh:mm:ss')
}
},
{
title: '备注',
dataIndex: 'note'
},
{
title: '描述',
dataIndex: 'description'
},
{
title: '操作',
render: (_, record) => (
<div>
</div>
)
}
];
const changePage = function(pageNo: number, pageSize: number) {
setPageNo(pageNo);
setPageSize(pageSize);
}
return <div id="bookingManage-container">
<div className="bookingManage-table">
<Table columns={columns} dataSource={bookingSearchResult} pagination={ {
current: pageNo,
pageSize: pageSize,
onChange: changePage
}}/>
</div>
</div>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
列表接口是这样的:
根据这个指定表格列的定义,并且添加分页的处理。
其中涉及到的 User 和 Room 的类型从其他页面导入:
这里用到 dayjs 来格式化日期,安装下:
npm install dayjs
然后加上上面的表单:
import { Button, DatePicker, Form, Input, Popconfirm, Table, TimePicker, message } from "antd";
import { useEffect, useState } from "react";
import { ColumnsType } from "antd/es/table";
import { useForm } from "antd/es/form/Form";
import './booking_manage.css';
import { UserSearchResult } from "../UserManage/UserManage";
import { MeetingRoomSearchResult } from "../MeetingRoomManage/MeetingRoomManage";
import dayjs from "dayjs";
export interface SearchBooking {
username: string;
meetingRoomName: string;
meetingRoomPosition: string;
rangeStartDate: Date;
rangeStartTime: Date;
rangeEndDate: Date;
rangeEndTime: Date;
}
interface BookingSearchResult {
id: number;
startTime: string;
endTime: string;
status: string;
note: string;
createTime: string;
updateTime: string;
user: UserSearchResult,
room: MeetingRoomSearchResult
}
export function BookingManage() {
const [pageNo, setPageNo] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [bookingSearchResult, setBookingSearchResult] = useState<Array<BookingSearchResult>>([]);
const [num, setNum] = useState(0);
const columns: ColumnsType<BookingSearchResult> = [
{
title: '会议室名称',
dataIndex: 'room',
render(_, record) {
return record.room.name
}
},
{
title: '会议室位置',
dataIndex: 'room',
render(_, record) {
return record.room.location
}
},
{
title: '预定人',
dataIndex: 'user',
render(_, record) {
return record.user.username
}
},
{
title: '开始时间',
dataIndex: 'startTime',
render(_, record) {
return dayjs(new Date(record.startTime)).format('YYYY-MM-DD HH:mm:ss')
}
},
{
title: '结束时间',
dataIndex: 'endTime',
render(_, record) {
return dayjs(new Date(record.endTime)).format('YYYY-MM-DD HH:mm:ss')
}
},
{
title: '审批状态',
dataIndex: 'status'
},
{
title: '预定时间',
dataIndex: 'createTime',
render(_, record) {
return dayjs(new Date(record.createTime)).format('YYYY-MM-DD hh:mm:ss')
}
},
{
title: '备注',
dataIndex: 'note'
},
{
title: '描述',
dataIndex: 'description'
},
{
title: '操作',
render: (_, record) => (
<div>
</div>
)
}
];
const searchBooking = async (values: SearchBooking) => {
}
const [form ] = useForm();
useEffect(() => {
searchBooking({
username: form.getFieldValue('username'),
meetingRoomName: form.getFieldValue('meetingRoomName'),
meetingRoomPosition: form.getFieldValue('meetingRoomPosition'),
rangeStartDate: form.getFieldValue('rangeStartDate'),
rangeStartTime: form.getFieldValue('rangeStartTime'),
rangeEndDate: form.getFieldValue('rangeEndDate'),
rangeEndTime: form.getFieldValue('rangeEndTime')
});
}, [pageNo, pageSize, num]);
const changePage = function(pageNo: number, pageSize: number) {
setPageNo(pageNo);
setPageSize(pageSize);
}
return <div id="bookingManage-container">
<div className="bookingManage-form">
<Form
form={form}
onFinish={searchBooking}
name="search"
layout='inline'
colon={false}
>
<Form.Item label="预定人" name="username">
<Input />
</Form.Item>
<Form.Item label="会议室名称" name="meetingRoomName">
<Input />
</Form.Item>
<Form.Item label="预定开始日期" name="rangeStartDate">
<DatePicker/>
</Form.Item>
<Form.Item label="预定开始时间" name="rangeStartTime">
<TimePicker/>
</Form.Item>
<Form.Item label="预定结束日期" name="rangeEndDate">
<DatePicker/>
</Form.Item>
<Form.Item label="预定结束时间" name="rangeEndTime">
<TimePicker/>
</Form.Item>
<Form.Item label="位置" name="meetingRoomPosition">
<Input />
</Form.Item>
<Form.Item label=" ">
<Button type="primary" htmlType="submit">
搜索预定申请
</Button>
</Form.Item>
</Form>
</div>
<div className="bookingManage-table">
<Table columns={columns} dataSource={bookingSearchResult} pagination={ {
current: pageNo,
pageSize: pageSize,
onChange: changePage
}}/>
</div>
</div>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
涉及到的 css 如下:
#bookingManage-container {
padding: 20px;
}
#bookingManage-container .bookingManage-form {
margin-bottom: 40px;
}
#bookingManage-container .ant-form-item {
margin: 10px;
}
2
3
4
5
6
7
8
9
10
渲染出来是这样的:
这里要注意的是日期和时间分别要用 DatePicker 和 TimePicker,所以分为 2 个字段。
接下来实现下用到的接口,改下 interfaces.ts
export async function bookingList(searchBooking: SearchBooking, pageNo: number, pageSize: number) {
let bookingTimeRangeStart;
let bookingTimeRangeEnd;
if(searchBooking.rangeStartDate && searchBooking.rangeStartTime) {
const rangeStartDateStr = dayjs(searchBooking.rangeStartDate).format('YYYY-MM-DD');
const rangeStartTimeStr = dayjs(searchBooking.rangeStartTime).format('HH:mm');
bookingTimeRangeStart = dayjs(rangeStartDateStr + ' ' + rangeStartTimeStr).valueOf()
}
if(searchBooking.rangeEndDate && searchBooking.rangeEndTime) {
const rangeEndDateStr = dayjs(searchBooking.rangeEndDate).format('YYYY-MM-DD');
const rangeEndTimeStr = dayjs(searchBooking.rangeEndTime).format('HH:mm');
bookingTimeRangeEnd = dayjs(rangeEndDateStr + ' ' + rangeEndTimeStr).valueOf()
}
return await axiosInstance.get('/booking/list', {
params: {
username: searchBooking.username,
meetingRoomName: searchBooking.meetingRoomName,
meetingRoomPosition: searchBooking.meetingRoomPosition,
bookingTimeRangeStart,
bookingTimeRangeEnd,
pageNo: pageNo,
pageSize: pageSize
}
});
}
export async function apply(id: number) {
return await axiosInstance.get('/booking/apply/' + id);
}
export async function reject(id: number) {
return await axiosInstance.get('/booking/reject/' + id);
}
export async function unbind(id: number) {
return await axiosInstance.get('/booking/unbind/' + id);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apply、reject、unbind 接口比较简单,列表接口相对麻烦一些。
因为现在日期和时间分为了 2 个字段,而接口只接收一个字段,所以要把它们合并。
用 dayjs 分别把日期和时间 format 成 YYYY-MM-DD 和 HH:mm 的格式。
然后拼接成一个字符串之后,再创建 dayjs 实例,这样时间就合并成一个了。
在页面里调用下列表接口:
import { Button, DatePicker, Form, Input, Popconfirm, Table, TimePicker, message } from "antd";
import { useEffect, useState } from "react";
import { ColumnsType } from "antd/es/table";
import { useForm } from "antd/es/form/Form";
import { apply, bookingList, reject, unbind } from "../../interfaces/interfaces";
import './booking_manage.css';
import { UserSearchResult } from "../UserManage/UserManage";
import { MeetingRoomSearchResult } from "../MeetingRoomManage/MeetingRoomManage";
import dayjs from "dayjs";
export interface SearchBooking {
username: string;
meetingRoomName: string;
meetingRoomPosition: string;
rangeStartDate: Date;
rangeStartTime: Date;
rangeEndDate: Date;
rangeEndTime: Date;
}
interface BookingSearchResult {
id: number;
startTime: string;
endTime: string;
status: string;
note: string;
createTime: string;
updateTime: string;
user: UserSearchResult,
room: MeetingRoomSearchResult
}
export function BookingManage() {
const [pageNo, setPageNo] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [bookingSearchResult, setBookingSearchResult] = useState<Array<BookingSearchResult>>([]);
const [num, setNum] = useState(0);
const columns: ColumnsType<BookingSearchResult> = [
{
title: '会议室名称',
dataIndex: 'room',
render(_, record) {
return record.room.name
}
},
{
title: '会议室位置',
dataIndex: 'room',
render(_, record) {
return record.room.location
}
},
{
title: '预定人',
dataIndex: 'user',
render(_, record) {
return record.user.username
}
},
{
title: '开始时间',
dataIndex: 'startTime',
render(_, record) {
return dayjs(new Date(record.startTime)).format('YYYY-MM-DD HH:mm:ss')
}
},
{
title: '结束时间',
dataIndex: 'endTime',
render(_, record) {
return dayjs(new Date(record.endTime)).format('YYYY-MM-DD HH:mm:ss')
}
},
{
title: '审批状态',
dataIndex: 'status',
},
{
title: '预定时间',
dataIndex: 'createTime',
render(_, record) {
return dayjs(new Date(record.createTime)).format('YYYY-MM-DD hh:mm:ss')
}
},
{
title: '备注',
dataIndex: 'note'
},
{
title: '描述',
dataIndex: 'description'
},
{
title: '操作',
render: (_, record) => (
<div>
</div>
)
}
];
const searchBooking = async (values: SearchBooking) => {
const res = await bookingList(values, pageNo, pageSize);
const { data } = res.data;
if(res.status === 201 || res.status === 200) {
setBookingSearchResult(data.bookings.map((item: BookingSearchResult) => {
return {
key: item.id,
...item
}
}))
} else {
message.error(data || '系统繁忙,请稍后再试');
}
}
const [form ] = useForm();
useEffect(() => {
searchBooking({
username: form.getFieldValue('username'),
meetingRoomName: form.getFieldValue('meetingRoomName'),
meetingRoomPosition: form.getFieldValue('meetingRoomPosition'),
rangeStartDate: form.getFieldValue('rangeStartDate'),
rangeStartTime: form.getFieldValue('rangeStartTime'),
rangeEndDate: form.getFieldValue('rangeEndDate'),
rangeEndTime: form.getFieldValue('rangeEndTime')
});
}, [pageNo, pageSize, num]);
const changePage = function(pageNo: number, pageSize: number) {
setPageNo(pageNo);
setPageSize(pageSize);
}
return <div id="bookingManage-container">
<div className="bookingManage-form">
<Form
form={form}
onFinish={searchBooking}
name="search"
layout='inline'
colon={false}
>
<Form.Item label="预定人" name="username">
<Input />
</Form.Item>
<Form.Item label="会议室名称" name="meetingRoomName">
<Input />
</Form.Item>
<Form.Item label="预定开始日期" name="rangeStartDate">
<DatePicker/>
</Form.Item>
<Form.Item label="预定开始时间" name="rangeStartTime">
<TimePicker/>
</Form.Item>
<Form.Item label="预定结束日期" name="rangeEndDate">
<DatePicker/>
</Form.Item>
<Form.Item label="预定结束时间" name="rangeEndTime">
<TimePicker/>
</Form.Item>
<Form.Item label="位置" name="meetingRoomPosition">
<Input />
</Form.Item>
<Form.Item label=" ">
<Button type="primary" htmlType="submit">
搜索预定申请
</Button>
</Form.Item>
</Form>
</div>
<div className="bookingManage-table">
<Table columns={columns} dataSource={bookingSearchResult} pagination={ {
current: pageNo,
pageSize: pageSize,
onChange: changePage
}}/>
</div>
</div>
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
没带参数的搜索没问题:
数据库里就这 4 条记录:
然后带上参数搜索下:
开始时间在 2023-9-29 的 10 点到 11 点的预定有 3 条:
11 点到 12 点的有 1 条:
这样,列表功能就完成了。
可以再加上个按照状态过滤,这个是 antd 的功能:
{
title: '审批状态',
dataIndex: 'status',
onFilter: (value, record) => record.status.startsWith(value as string),
filters: [
{
text: '审批通过',
value: '审批通过',
},
{
text: '审批驳回',
value: '审批驳回',
},
{
text: '申请中',
value: '申请中',
},
{
text: '已解除',
value: '已解除'
},
],
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
然后加上右边的按钮:
{
title: '操作',
render: (_, record) => (
<div>
<Popconfirm
title="通过申请"
description="确认通过吗?"
onConfirm={() => changeStatus(record.id, 'apply')}
okText="Yes"
cancelText="No"
>
<a href="#">通过</a>
</Popconfirm>
<br/>
<Popconfirm
title="驳回申请"
description="确认驳回吗?"
onConfirm={() => changeStatus(record.id, 'reject')}
okText="Yes"
cancelText="No"
>
<a href="#">驳回</a>
</Popconfirm>
<br/>
<Popconfirm
title="解除申请"
description="确认解除吗?"
onConfirm={() => changeStatus(record.id, 'unbind')}
okText="Yes"
cancelText="No"
>
<a href="#">解除</a>
</Popconfirm>
<br/>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
async function changeStatus(id: number, status: 'apply' | 'reject' | 'unbind') {
const methods = {
apply,
reject,
unbind
}
const res = await methods[status](id);
if(res.status === 201 || res.status === 200) {
message.success('状态更新成功');
setNum(Math.random());
} else {
message.error(res.data.data);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
更新完状态之后要触发列表的重新渲染,所以这里用 setNum 触发:
这样,预定管理的功能就完成了。
案例代码上传了小册仓库 (opens new window)
# 总结
这节我们实现了预订管理的管理端的前端页面,主要是列表和修改状态的接口。
要注意的是时间相关的处理,antd 只有 DatePicker 和 TimePicker,我们要添加 2 个字段接收,然后调用接口的时候把它们合并成一个字段。
下节我们来写用户端的部分。