这节我们来写下预订管理模块的用户端。
把用户端代码跑起来,首先写下预订历史页面:
这个页面就是一个列表:
我们先写上面的 form:
import { Button, DatePicker, Form, Input, TimePicker } from "antd";
import { useEffect } from "react";
import { useForm } from "antd/es/form/Form";
import './booking_history.css';
export interface SearchBooking {
username: string;
meetingRoomName: string;
meetingRoomPosition: string;
rangeStartDate: Date;
rangeStartTime: Date;
rangeEndDate: Date;
rangeEndTime: Date;
}
export function BookingHistory() {
const searchBooking = async (values: SearchBooking) => {
}
const [form ] = useForm();
useEffect(() => {
searchBooking({
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')
});
}, []);
return <div id="bookingHistory-container">
<div className="bookingHistory-form">
<Form
form={form}
onFinish={searchBooking}
name="search"
layout='inline'
colon={false}
>
<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>
}
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
#bookingHistory-container {
padding: 20px;
}
#bookingHistory-container .bookingHistory-form {
margin-bottom: 40px;
}
#bookingHistory-container .ant-form-item {
margin: 10px;
}
2
3
4
5
6
7
8
9
10
和后台管理的页面差不多,只不过这里没有 user 的搜索。
我们登录的时候把它放到了 localStorage 里,所以这里从 localStorage 取就行:
function getUserInfo() {
const userInfoStr = localStorage.getItem('user_info');
if(userInfoStr) {
return JSON.parse(userInfoStr);
}
}
2
3
4
5
6
7
然后在 interface.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
}
});
}
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
这个就是当时后台管理的接口,没啥区别。
然后在页面调用下:
interface BookingSearchResult {
id: number;
startTime: string;
endTime: string;
status: string;
note: string;
createTime: string;
updateTime: string;
room: MeetingRoomSearchResult
}
2
3
4
5
6
7
8
9
10
const [pageNo, setPageNo] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [bookingSearchResult, setBookingSearchResult] = useState<Array<BookingSearchResult>>([]);
const searchBooking = async (values: SearchBooking) => {
const res = await bookingList({
...values,
username: getUserInfo().username
}, 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 || '系统繁忙,请稍后再试');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
接口调用没啥问题:
然后加上下面的表格:
这些和后台管理一样,直接复制过来就行:
import { Button, DatePicker, Form, Input, Table, TimePicker, message } from "antd";
import { useEffect, useState } from "react";
import { useForm } from "antd/es/form/Form";
import './booking_history.css';
import { bookingList } from "../../interface/interfaces";
import { MeetingRoomSearchResult } from "../meeting_room_list/MeetingRoomList";
import { ColumnsType } from "antd/es/table";
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;
room: MeetingRoomSearchResult
}
function getUserInfo() {
const userInfoStr = localStorage.getItem('user_info');
if(userInfoStr) {
return JSON.parse(userInfoStr);
}
}
export function BookingHistory() {
const [pageNo, setPageNo] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [bookingSearchResult, setBookingSearchResult] = useState<Array<BookingSearchResult>>([]);
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();
const changePage = function(pageNo: number, pageSize: number) {
setPageNo(pageNo);
setPageSize(pageSize);
}
useEffect(() => {
searchBooking({
username: getUserInfo().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]);
const columns: ColumnsType<BookingSearchResult> = [
{
title: '会议室名称',
dataIndex: 'room',
render(_, record) {
return record.room.name
}
},
{
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',
onFilter: (value, record) => record.status.startsWith(value as string),
filters: [
{
text: '审批通过',
value: '审批通过',
},
{
text: '审批驳回',
value: '审批驳回',
},
{
text: '申请中',
value: '申请中',
},
{
text: '已解除',
value: '已解除'
},
],
},
{
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: () => (
<div>
</div>
)
}
];
return <div id="bookingHistory-container">
<div className="bookingHistory-form">
<Form
form={form}
onFinish={searchBooking}
name="search"
layout='inline'
colon={false}
>
<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="bookingHistory-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
192
193
194
195
196
197
这样,列表就完成了:
然后实现解除预定功能:
在 interface.ts 添加 unbind 接口:
export async function unbind(id: number) {
return await axiosInstance.get('/booking/unbind/' + id);
}
2
3
然后在页面调用下:
import { Button, DatePicker, Form, Input, Popconfirm, Table, TimePicker, message } from "antd";
import { useEffect, useState } from "react";
import { useForm } from "antd/es/form/Form";
import './booking_history.css';
import { bookingList, unbind } from "../../interface/interfaces";
import { MeetingRoomSearchResult } from "../meeting_room_list/MeetingRoomList";
import { ColumnsType } from "antd/es/table";
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;
room: MeetingRoomSearchResult
}
function getUserInfo() {
const userInfoStr = localStorage.getItem('user_info');
if(userInfoStr) {
return JSON.parse(userInfoStr);
}
}
export function BookingHistory() {
const [pageNo, setPageNo] = useState<number>(1);
const [pageSize, setPageSize] = useState<number>(10);
const [bookingSearchResult, setBookingSearchResult] = useState<Array<BookingSearchResult>>([]);
const [num, setNum] = useState(0);
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();
const changePage = function(pageNo: number, pageSize: number) {
setPageNo(pageNo);
setPageSize(pageSize);
}
useEffect(() => {
searchBooking({
username: getUserInfo().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]);
async function changeStatus(id: number) {
const res = await unbind(id);
if(res.status === 201 || res.status === 200) {
message.success('状态更新成功');
setNum(Math.random());
} else {
message.error(res.data.data);
}
}
const columns: ColumnsType<BookingSearchResult> = [
{
title: '会议室名称',
dataIndex: 'room',
render(_, record) {
return record.room.name
}
},
{
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',
onFilter: (value, record) => record.status.startsWith(value as string),
filters: [
{
text: '审批通过',
value: '审批通过',
},
{
text: '审批驳回',
value: '审批驳回',
},
{
text: '申请中',
value: '申请中',
},
{
text: '已解除',
value: '已解除'
},
],
},
{
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) => (
record.status === '申请中' ? <div>
<Popconfirm
title="解除申请"
description="确认解除吗?"
onConfirm={() => changeStatus(record.id)}
okText="Yes"
cancelText="No"
>
<a href="#">解除预定</a>
</Popconfirm>
</div> : null
)
}
];
return <div id="bookingHistory-container">
<div className="bookingHistory-form">
<Form
form={form}
onFinish={searchBooking}
name="search"
layout='inline'
colon={false}
>
<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="bookingHistory-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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
测试下:
没啥问题。
如果没有合适的数据,就手动去数据库里改一下:
接下来,还有一个添加预定的功能:
当点击会议室列表的预定按钮的时候,会弹出这个窗口。
添加 src/meeting_room_list/CreateBookingModal.tsx
import { DatePicker, Form, Input, InputNumber, Modal, Select, TimePicker, message } from "antd";
import { useForm } from "antd/es/form/Form";
import { bookingAdd } from "../../interface/interfaces";
import { MeetingRoomSearchResult } from "./MeetingRoomList";
interface CreateBookingModalProps {
isOpen: boolean;
handleClose: Function;
meetingRoom: MeetingRoomSearchResult;
}
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 }
}
export interface CreateBooking {
meetingRoomId: number;
rangeStartDate: Date;
rangeStartTime: Date;
rangeEndDate: Date;
rangeEndTime: Date;
note: string;
}
export function CreateBookingModal(props: CreateBookingModalProps) {
const [form] = useForm<CreateBooking>();
const handleOk = async function() {
}
return <Modal title="创建会议室" open={props.isOpen} onOk={handleOk} onCancel={() => props.handleClose()} okText={'创建'}>
<Form
form={form}
colon={false}
{...layout}
>
<Form.Item
label="会议室名称"
name="meetingRoomId"
>
{props.meetingRoom.name}
</Form.Item>
<Form.Item
label="预定开始日期"
name="rangeStartDate"
rules={[
{ required: true, message: '请输入预定开始日期!' },
]}
>
<DatePicker/>
</Form.Item>
<Form.Item
label="预定开始时间"
name="rangeStartTime"
rules={[
{ required: true, message: '请输入预定开始日期!' },
]}
>
<TimePicker/>
</Form.Item>
<Form.Item
label="预定结束日期"
name="rangeEndDate"
rules={[
{ required: true, message: '请输入预定结束日期!' },
]}
>
<DatePicker/>
</Form.Item>
<Form.Item
label="预定结束时间"
name="rangeEndTime"
rules={[
{ required: true, message: '请输入预定结束日期!' },
]}
>
<TimePicker/>
</Form.Item>
<Form.Item
label="备注"
name="note"
>
<Input />
</Form.Item>
</Form>
</Modal>
}
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
然后点击预定按钮的时候,显示这个弹窗:
添加 isCreateModalOpen 的 state 来标识弹窗是否打开,并且记录点击的是哪个会议室:
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [currentMeetingRoom, setCurrentMeetingRoom] = useState<MeetingRoomSearchResult>();
2
点击预定按钮的时候,打开弹窗,记录当前会议室:
{
title: '操作',
render: (_, record) => (
<div>
<a href="#" onClick={() => {
setIsCreateModalOpen(true);
setCurrentMeetingRoom(record);
}}>预定</a>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
然后弹窗传入当前的会议室,并且点击关闭的时候关闭弹窗:
{
currentMeetingRoom ?
<CreateBookingModal meetingRoom={currentMeetingRoom} isOpen={isCreateModalOpen} handleClose={() => {
setIsCreateModalOpen(false);
}}></CreateBookingModal>
: null
}
2
3
4
5
6
7
没啥问题:
然后在 interface.ts 添加用到的接口:
export async function bookingAdd(booking: CreateBooking) {
const rangeStartDateStr = dayjs(booking.rangeStartDate).format('YYYY-MM-DD');
const rangeStartTimeStr = dayjs(booking.rangeStartTime).format('HH:mm');
const startTime = dayjs(rangeStartDateStr + ' ' + rangeStartTimeStr).valueOf()
const rangeEndDateStr = dayjs(booking.rangeEndDate).format('YYYY-MM-DD');
const rangeEndTimeStr = dayjs(booking.rangeEndTime).format('HH:mm');
const endTime = dayjs(rangeEndDateStr + ' ' + rangeEndTimeStr).valueOf()
return await axiosInstance.post('/booking/add', {
meetingRoomId: booking.meetingRoomId,
startTime,
endTime,
note: booking.note
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这里需要把日期时间做合并。
然后在组件里调用下:
const handleOk = async function() {
const values = form.getFieldsValue();
values.meetingRoomId = props.meetingRoom.id;
const res = await bookingAdd(values);
if(res.status === 201 || res.status === 200) {
message.success('预定成功');
form.resetFields();
props.handleClose();
} else {
message.error(res.data.data);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
没啥问题:
这样,预定、预订历史、取消预订就都完成了。
案例代码上传了小册仓库 (opens new window)
# 总结
这节我们完成了预订历史、添加预定、取消预订的功能。
就是涉及到时间日期需要两个表单做合并处理,其余的倒是没啥难度。
至此,预订管理模块就完成了。