Skip to content

上节写了统计用的两个接口,这节来加一下 swagger 文档,然后写下前端部分。

这个接口有 2 个 query 参数,返回值是一个对象,所以这样写:

javascript
@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

javascript
import { ApiProperty } from "@nestjs/swagger";

export class UserBookignCount {

    @ApiProperty()
    userId: string;

    @ApiProperty()
    username: string;

    @ApiProperty()
    bookingCount: string;
}

访问下 http://localhost:3005/api-doc

可以看到这个接口的文档:

没啥问题。

然后添加另一个接口的:

javascript
@ApiBearerAuth()
@ApiQuery({
    name: 'startTime',
    type: String,
    description: '开始时间'
})
@ApiQuery({
    name: 'endTime',
    type: String,
    description: '结束时间'
})
@ApiResponse({
    status: HttpStatus.OK,
    type: MeetingRoomUsedCount
})

src/statistic/MeetingRoomUsedCount.vo.ts

javascript
import { ApiProperty } from "@nestjs/swagger";

export class MeetingRoomUsedCount {

    @ApiProperty()
    meetingRoomId: string;

    @ApiProperty()
    meetingRoomName: string;

    @ApiProperty()
    usedCount: string;
}

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

这样,swagger 文档就完成了。

然后来写前端代码:

统计的路由我们已经写过了,只要填内容就行。

原型图是这样的:

加个 antd 的 Form,然后再用 echarts 的图表展示下数据就好了。

先加下 form:

javascript
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:

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 的柱状图:

javascript
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 里加一下:

javascript
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 来存储返回的数据,然后点击查询的时候请求接口:

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

javascript
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>
}

这样,点击查询的时候就会根据返回的数据渲染柱形图:

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

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

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

过程一摸一样。

javascript
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 就行。

至此,所有模块的钱后端代码就都完成了。