这节写下预定管理模块管理端的前端部分:
对应的路由我们前面写过了:
这节来填充下内容。
首先是 table 部分:
javascript
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>
}
列表接口是这样的:
根据这个指定表格列的定义,并且添加分页的处理。
其中涉及到的 User 和 Room 的类型从其他页面导入:
这里用到 dayjs 来格式化日期,安装下:
npm install dayjs
然后加上上面的表单:
javascript
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>
}
涉及到的 css 如下:
css
#bookingManage-container {
padding: 20px;
}
#bookingManage-container .bookingManage-form {
margin-bottom: 40px;
}
#bookingManage-container .ant-form-item {
margin: 10px;
}
渲染出来是这样的:
这里要注意的是日期和时间分别要用 DatePicker 和 TimePicker,所以分为 2 个字段。
接下来实现下用到的接口,改下 interfaces.ts
javascript
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);
}
apply、reject、unbind 接口比较简单,列表接口相对麻烦一些。
因为现在日期和时间分为了 2 个字段,而接口只接收一个字段,所以要把它们合并。
用 dayjs 分别把日期和时间 format 成 YYYY-MM-DD 和 HH:mm 的格式。
然后拼接成一个字符串之后,再创建 dayjs 实例,这样时间就合并成一个了。
在页面里调用下列表接口:
javascript
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>
}
没带参数的搜索没问题:
数据库里就这 4 条记录:
然后带上参数搜索下:
开始时间在 2023-9-29 的 10 点到 11 点的预定有 3 条:
11 点到 12 点的有 1 条:
这样,列表功能就完成了。
可以再加上个按照状态过滤,这个是 antd 的功能:
javascript
{
title: '审批状态',
dataIndex: 'status',
onFilter: (value, record) => record.status.startsWith(value as string),
filters: [
{
text: '审批通过',
value: '审批通过',
},
{
text: '审批驳回',
value: '审批驳回',
},
{
text: '申请中',
value: '申请中',
},
{
text: '已解除',
value: '已解除'
},
],
},
然后加上右边的按钮:
javascript
{
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>
)
}
javascript
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);
}
}
更新完状态之后要触发列表的重新渲染,所以这里用 setNum 触发:
这样,预定管理的功能就完成了。
案例代码上传了小册仓库
总结
这节我们实现了预订管理的管理端的前端页面,主要是列表和修改状态的接口。
要注意的是时间相关的处理,antd 只有 DatePicker 和 TimePicker,我们要添加 2 个字段接收,然后调用接口的时候把它们合并成一个字段。
下节我们来写用户端的部分。