上节写了统计用的两个接口,这节来加一下 swagger 文档,然后写下前端部分。
这个接口有 2 个 query 参数,返回值是一个对象,所以这样写:

@ApiBearerAuth()
@ApiQuery({
name: 'startTime',
type: String,
description: '开始时间'
})
@ApiQuery({
name: 'endTime',
type: String,
description: '结束时间'
})
@ApiResponse({
status: HttpStatus.OK,
type: UserBookignCount
})
涉及到的 vo 在 src/statistic/vo/UserBookignCount.vo.ts
import { ApiProperty } from "@nestjs/swagger";
export class UserBookignCount {
@ApiProperty()
userId: string;
@ApiProperty()
username: string;
@ApiProperty()
bookingCount: string;
}
访问下 http://localhost:3005/api-doc
可以看到这个接口的文档:

没啥问题。
然后添加另一个接口的:

@ApiBearerAuth()
@ApiQuery({
name: 'startTime',
type: String,
description: '开始时间'
})
@ApiQuery({
name: 'endTime',
type: String,
description: '结束时间'
})
@ApiResponse({
status: HttpStatus.OK,
type: MeetingRoomUsedCount
})
src/statistic/MeetingRoomUsedCount.vo.ts
import { ApiProperty } from "@nestjs/swagger";
export class MeetingRoomUsedCount {
@ApiProperty()
meetingRoomId: string;
@ApiProperty()
meetingRoomName: string;
@ApiProperty()
usedCount: string;
}

然后再加个 @ApiTags 把这俩接口文档分成一组:


这样,swagger 文档就完成了。
然后来写前端代码:

统计的路由我们已经写过了,只要填内容就行。
原型图是这样的:


加个 antd 的 Form,然后再用 echarts 的图表展示下数据就好了。
先加下 form:
import { Button, DatePicker, Form, Select } from "antd";
import "./statistics.css";
export function Statistics() {
function getStatisticData(values: { startTime: string; endTime: string }) {
console.log(values);
}
return (
<div id="statistics-container">
<div className="statistics-form">
<Form
onFinish={getStatisticData}
name="search"
layout="inline"
colon={false}>
<Form.Item label="开始日期" name="startTime">
<DatePicker />
</Form.Item>
<Form.Item label="结束日期" name="endTime">
<DatePicker />
</Form.Item>
<Form.Item
label="图表类型"
name="chartType"
initialValue={"bar"}>
<Select>
<Select.Option value="pie">饼图</Select.Option>
<Select.Option value="bar">柱形图</Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
查询
</Button>
</Form.Item>
</Form>
</div>
<div className="statistics-chart">图表</div>
</div>
);
}
css:
#statistics-container {
padding: 20px;
}
#statistics-container .statistics-form {
margin-bottom: 40px;
}
#statistics-container .statistics-chart {
width: 800px;
height: 600px;
}
点击查询,会打印 form 的值:

然后安装 echarts:
npm install echarts --save
然后通过 useRef 拿到 dom 元素,再初始化下 echarts 的柱状图:


import { Button, DatePicker, Form, Select } from "antd";
import "./statistics.css";
import * as echarts from "echarts";
import { useEffect, useRef } from "react";
export function Statistics() {
const containerRef = useRef<HTMLDivElement>(null);
function getStatisticData(values: { startTime: string; endTime: string }) {
console.log(values);
}
useEffect(() => {
const myChart = echarts.init(containerRef.current);
myChart.setOption({
title: {
text: "ECharts 入门示例",
},
tooltip: {},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
},
yAxis: {},
series: [
{
name: "销量",
type: "bar",
data: [5, 20, 36, 10, 10, 20],
},
],
});
}, []);
return (
<div id="statistics-container">
<div className="statistics-form">
<Form
onFinish={getStatisticData}
name="search"
layout="inline"
colon={false}>
<Form.Item label="开始日期" name="startTime">
<DatePicker />
</Form.Item>
<Form.Item label="结束日期" name="endTime">
<DatePicker />
</Form.Item>
<Form.Item
label="图表类型"
name="chartType"
initialValue={"bar"}>
<Select>
<Select.Option value="pie">饼图</Select.Option>
<Select.Option value="bar">柱形图</Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
查询
</Button>
</Form.Item>
</Form>
</div>
<div className="statistics-chart" ref={containerRef}></div>
</div>
);
}
这样 echarts 就成功引入了:

然后我们加一下接口:
在 src/interface/interfaces.ts 里加一下:
export async function meetingRoomUsedCount(startTime: string, endTime: string) {
return await axiosInstance.get("/statistic/meetingRoomUsedCount", {
params: {
startTime,
endTime,
},
});
}
export async function userBookingCount(startTime: string, endTime: string) {
return await axiosInstance.get("/statistic/userBookingCount", {
params: {
startTime,
endTime,
},
});
}
我们加一个 state 来存储返回的数据,然后点击查询的时候请求接口:

当数据变化的时候,渲染图表:

import { Button, DatePicker, Form, Select, message } from "antd";
import "./statistics.css";
import * as echarts from "echarts";
import { useEffect, useRef, useState } from "react";
import { userBookingCount } from "../../interfaces/interfaces";
import dayjs from "dayjs";
interface UserBookingData {
userId: string;
username: string;
bookingCount: string;
}
export function Statistics() {
const [userBookingData, setUserBookingData] =
useState<Array<UserBookingData>>();
const containerRef = useRef<HTMLDivElement>(null);
async function getStatisticData(values: {
startTime: string;
endTime: string;
}) {
const startTime = dayjs(values.startTime).format("YYYY-MM-DD");
const endTime = dayjs(values.endTime).format("YYYY-MM-DD");
const res = await userBookingCount(startTime, endTime);
const { data } = res.data;
if (res.status === 201 || res.status === 200) {
setUserBookingData(data);
} else {
message.error(data || "系统繁忙,请稍后再试");
}
}
useEffect(() => {
const myChart = echarts.init(containerRef.current);
if (!userBookingData) {
return;
}
myChart.setOption({
title: {
text: "用户预定情况",
},
tooltip: {},
xAxis: {
data: userBookingData?.map((item) => item.username),
},
yAxis: {},
series: [
{
name: "预定次数",
type: "bar",
data: userBookingData?.map((item) => {
return {
name: item.username,
value: item.bookingCount,
};
}),
},
],
});
}, [userBookingData]);
return (
<div id="statistics-container">
<div className="statistics-form">
<Form
onFinish={getStatisticData}
name="search"
layout="inline"
colon={false}>
<Form.Item label="开始日期" name="startTime">
<DatePicker />
</Form.Item>
<Form.Item label="结束日期" name="endTime">
<DatePicker />
</Form.Item>
<Form.Item
label="图表类型"
name="chartType"
initialValue={"bar"}>
<Select>
<Select.Option value="pie">饼图</Select.Option>
<Select.Option value="bar">柱形图</Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
查询
</Button>
</Form.Item>
</Form>
</div>
<div className="statistics-chart" ref={containerRef}></div>
</div>
);
}
这样,点击查询的时候就会根据返回的数据渲染柱形图:

然后我们再加上饼图的部分:


这样,统计的图表就完成了:

我们在下面再加一个会议室使用情况的图表。



过程一摸一样。
import { Button, DatePicker, Form, Select, message } from "antd";
import "./statistics.css";
import * as echarts from "echarts";
import { useEffect, useRef, useState } from "react";
import {
meetingRoomUsedCount,
userBookingCount,
} from "../../interfaces/interfaces";
import dayjs from "dayjs";
import { useForm } from "antd/es/form/Form";
interface UserBookingData {
userId: string;
username: string;
bookingCount: string;
}
interface MeetingRoomUsedData {
meetingRoomName: string;
meetingRoomId: number;
usedCount: string;
}
export function Statistics() {
const [userBookingData, setUserBookingData] =
useState<Array<UserBookingData>>();
const [meetingRoomUsedData, setMeetingRoomUsedData] =
useState<Array<MeetingRoomUsedData>>();
const containerRef = useRef<HTMLDivElement>(null);
const containerRef2 = useRef<HTMLDivElement>(null);
async function getStatisticData(values: {
startTime: string;
endTime: string;
}) {
const startTime = dayjs(values.startTime).format("YYYY-MM-DD");
const endTime = dayjs(values.endTime).format("YYYY-MM-DD");
const res = await userBookingCount(startTime, endTime);
const { data } = res.data;
if (res.status === 201 || res.status === 200) {
setUserBookingData(data);
} else {
message.error(data || "系统繁忙,请稍后再试");
}
const res2 = await meetingRoomUsedCount(startTime, endTime);
const { data: data2 } = res2.data;
if (res2.status === 201 || res2.status === 200) {
setMeetingRoomUsedData(data2);
} else {
message.error(data2 || "系统繁忙,请稍后再试");
}
}
useEffect(() => {
const myChart = echarts.init(containerRef.current);
if (!userBookingData) {
return;
}
myChart.setOption({
title: {
text: "用户预定情况",
},
tooltip: {},
xAxis: {
data: userBookingData?.map((item) => item.username),
},
yAxis: {},
series: [
{
name: "预定次数",
type: form.getFieldValue("chartType"),
data: userBookingData?.map((item) => {
return {
name: item.username,
value: item.bookingCount,
};
}),
},
],
});
}, [userBookingData]);
useEffect(() => {
const myChart = echarts.init(containerRef2.current);
if (!meetingRoomUsedData) {
return;
}
myChart.setOption({
title: {
text: "会议室使用情况",
},
tooltip: {},
xAxis: {
data: meetingRoomUsedData?.map((item) => item.meetingRoomName),
},
yAxis: {},
series: [
{
name: "使用次数",
type: form.getFieldValue("chartType"),
data: meetingRoomUsedData?.map((item) => {
return {
name: item.meetingRoomName,
value: item.usedCount,
};
}),
},
],
});
}, [meetingRoomUsedData]);
const [form] = useForm();
return (
<div id="statistics-container">
<div className="statistics-form">
<Form
form={form}
onFinish={getStatisticData}
name="search"
layout="inline"
colon={false}>
<Form.Item label="开始日期" name="startTime">
<DatePicker />
</Form.Item>
<Form.Item label="结束日期" name="endTime">
<DatePicker />
</Form.Item>
<Form.Item
label="图表类型"
name="chartType"
initialValue={"bar"}>
<Select>
<Select.Option value="pie">饼图</Select.Option>
<Select.Option value="bar">柱形图</Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
查询
</Button>
</Form.Item>
</Form>
</div>
<div className="statistics-chart" ref={containerRef}></div>
<div className="statistics-chart" ref={containerRef2}></div>
</div>
);
}

代码在小册仓库。
总结
这节我们加了 swagger 文档并且写了统计管理模块的前端代码。
前端部分主要是 echarts 的图表,这个根据返回的数据调整下格式,然后设置到 echarts 的 options 就行。
至此,所有模块的钱后端代码就都完成了。
