初始化项目
1
| npx create-react-app crowller-frontend --template typescript
|
使用脚手架create-react-app
工具初始化项目,并设定模板为typescript
类型,当使用各种外部依赖时,关于typescript
的使用可查看文档,其中都会有栏目在 TypeScript 中使用,说明了如何在联动typescript
;
为了简化UI开发,使用第三发的组件库:
并在启动文件代码顶部中,引入对应的样式文件:
1
| import 'antd/dist/antd.css';
|
页面开发
依照后端的开发进程,我们主要是有两个页面:登录和主页,在src/
下,新建pages
目录,并创建对应目录:Login
&Home
,做好结构划分;
登录页
1 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
| import { Form, Input, Button } from "antd"; import { LockOutlined } from "@ant-design/icons"; import "./Login.css";
const LoginForm = () => { const onFinish = (values: {}) => { console.log("Received values of form: ", values); };
return ( <div className="login-page"> <Form initialValues={{ remember: true, }} onFinish={onFinish} > <Form.Item name="password" rules={[ { required: true, message: "请输入密码!", }, ]} > <Input prefix={<LockOutlined className="site-form-item-icon" />} type="password" placeholder="Password" /> </Form.Item>
<Form.Item> <Button type="primary" htmlType="submit" > 登录 </Button> </Form.Item> </Form> </div> ); };
export default LoginForm;
|
登录页简单起见,只写了一个卡片,内部是一个输入框和登录按钮;
主页及数据
状态数据inited
和isLogin
判断是否登录跳转页面,页面由三个按钮和数据图表组成,
1 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
| interface CourseItem { title: string; count: number; }
interface State { inited: boolean; isLogin: boolean; data: { [key: string]: CourseItem[]; }; } class Home extends Component { state: State = { inited: false, isLogin: true, data: {}, };
render() { const { isLogin, inited } = this.state; if (isLogin) { if (inited) { return ( <div className="home-page"> <div className="button-list"> <Row> <Col span={8}> <Button type="primary" size="middle" onClick={this.handleCrowller} > 爬取 </Button> </Col> <Col span={8}> <Button type="primary" size="middle"> 展示 </Button> </Col> <Col span={8}> <Button type="primary" size="middle" onClick={this.handleLogout} > 退出 </Button> </Col> </Row> </div> <ReactECharts option={this.getOption()} /> </div> ); } else { return null; } } else { return <Navigate to="/login" />; } } }
|
按钮的点击事件和生命周期函数,按钮点击分别触发爬取数据和退出登录,生命周期函数中检测登录,跳转页面:
1 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
| componentDidMount() { axios.get("/api/islogin").then(({ data }) => { if (!data.data) { this.setState({ isLogin: false }); } else { this.setState({ inited: true }); } }); axios.get("/api/show").then(({ data }) => { this.setState({ data, }); }); }
handleCrowller = () => { axios.get("/api/data").then(({ data }) => { if (data.data) { message.success("爬取成功"); } }); };
handleLogout = () => { axios.get("/api/logout").then(({ data }) => { console.log(data); if (data.data) { this.setState({ isLogin: false }); } else { message.error("退出失败"); } }); };
|
路由配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { BrowserRouter, Routes, Route } from "react-router-dom"; import Login from "./pages/login"; import Home from "./pages/home";
function App() { return ( <div className="App"> <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="/login" element={<Login />} /> </Routes> </BrowserRouter> </div> ); }
|
数据可视化
安装依赖,以及简单使用:
1 2
| npm install --save echarts-for-react npm install --save echarts
|
1 2 3
| import ReactECharts from 'echarts-for-react';
<ReactECharts option={this.getOption()} />
|
查看官网给出的[示例](Examples - Apache ECharts),选取符合要求的折线图表,并对数据进行处理:
1 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
| getOption() { const { data } = this.state; const courseNames: string[] = []; const times: string[] = []; const tempData: { [key: string]: number[]; } = {}; for (let i in data) { const item = data[i]; times.push(moment(Number(i)).format("MM-DD HH:mm")); item.forEach((innerItem) => { const { title, count } = innerItem; if (courseNames.indexOf(title) === -1) { courseNames.push(title); } tempData[title] ? tempData[title].push(count) : (tempData[title] = [count]); }); } const seriesData = []; for (let i in tempData) { seriesData.push({ name: i, type: "line", data: tempData[i], }); } return { title: { text: "学习人数", }, tooltip: { trigger: "axis", }, legend: { data: courseNames, }, grid: { left: "3%", right: "4%", bottom: "3%", containLabel: true, }, xAxis: { type: "category", boundaryGap: false, data: times, }, yAxis: { type: "value", }, series: seriesData, }; }
|
简单优化
新建request.ts
文件,对网络请求的结果做一层提取,没必要反复做data.data
的操作:
1 2 3 4 5 6 7 8 9
| import axios from "axios";
const http = axios.create({ baseURL: "/" });
http.interceptors.response.use((response) => { return response.data; });
export default http;
|