1.server.js
const express = require('express'),
bodyparser = require('body-parser'),
fs = require('fs').promises,
path = require('path');
const pathdb = path.resolve(__dirname, 'database'),
config = require('./package.json').config,
server = config.server,
{ open, safeList } = config.cros,
{ filter, responsePublic, nowTimeFn } = require('./utils');
/*-创建&启动服务-*/
const app = express();
app.listen(server, () => {
console.log(`THE WEB SERVICE SUCCESSFULLY AND LISTENING TO THE PORT:${server}!`);
});
/*-中间件-*/
if (open) {
app.use((req, res, next) => {
let origin = req.headers.origin || req.headers.referer || "";
console.log(21, origin)
origin = origin.replace(/\/$/g, '');
origin = !safeList.includes(origin) ? '' : origin;
console.log(24, origin)
res.header("Access-Control-Allow-Origin", origin);
res.header("Access-Control-Allow-Methods", 'GET,POST,DELETE,HEAD,OPTIONS,PATCH,PUT');
res.header("Access-Control-Allow-Headers", 'DNT,authorzation,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,x-token');
res.header("Access-Control-Allow-Credentials", true);
req.method === 'OPTIONS' ? res.send() : next();
});
}
app.use(bodyparser.urlencoded({ extended: false }));
app.use(async (req, _, next) => {
req.$taskDATA = filter(await fs.readFile(`${pathdb}/task.json`, 'utf-8'));
next();
});
/*-接口管理-*/
app.get('/getTaskList', async (req, res) => {
let $taskDATA = req.$taskDATA;
let { limit = 100, page = 1, state = 0 } = req.query;
$taskDATA.reverse();
if (+state !== 0) $taskDATA = $taskDATA.filter(item => +item['state'] === +state);
let total = $taskDATA.length,
pageNum = Math.ceil(total / limit),
list = [];
if (page <= pageNum) {
for (let i = (page - 1) * limit; i <= (page * limit - 1); i++) {
let item = $taskDATA[i];
if (!item) break;
list.push({
id: +item['id'],
task: item['task'],
state: item['state'],
time: item['time'],
complete: item['complete']
});
}
}
responsePublic(res, true, {
page,
pageNum,
total,
limit,
list
});
});
app.post('/addTask', async (req, res) => {
let $taskDATA = req.$taskDATA,
len = $taskDATA.length,
body = req.body,
nowTime = nowTimeFn();
body = {
id: len === 0 ? 1 : +($taskDATA[len - 1]['id']) + 1,
task: '',
state: 1,
time: nowTime,
complete: nowTime,
...body,
};
body.complete = body.time;
$taskDATA.push(body);
try {
await fs.writeFile(`${pathdb}/task.json`, JSON.stringify($taskDATA), 'utf-8');
responsePublic(res, true);
} catch (_) {
responsePublic(res, false);
}
});
app.get('/removeTask', async (req, res) => {
let $taskDATA = req.$taskDATA,
{ id = 0 } = req.query,
flag = false;
$taskDATA = $taskDATA.filter(item => {
if (+item.id === +id) {
flag = true;
return false;
}
return true;
});
if (!flag) {
responsePublic(res, false);
return;
}
try {
await fs.writeFile(`${pathdb}/task.json`, JSON.stringify($taskDATA), 'utf-8');
responsePublic(res, true);
} catch (_) {
responsePublic(res, false);
}
});
app.get('/completeTask', async (req, res) => {
let $taskDATA = req.$taskDATA,
{ id = 0 } = req.query,
flag = false;
$taskDATA = $taskDATA.map(item => {
if (+item.id === +id) {
flag = true;
item.state = 2;
item.complete = nowTimeFn();
}
return item;
});
if (!flag) {
responsePublic(res, false);
return;
}
try {
await fs.writeFile(`${pathdb}/task.json`, JSON.stringify($taskDATA), 'utf-8');
responsePublic(res, true);
} catch (_) {
responsePublic(res, false);
}
});
/*-静态页面&404-*/
app.use(express.static('./static'));
app.use((_, res) => {
res.status(404);
res.send();
});
2.antd组件库
antd组件库,自带按需导入,不用额外配置(用到了哪些组件,最后打包的时候就打包哪些组件),但是使用的时候还是得用到哪个解构哪个
配置汉化,引入全局样式index.less
index.lss中引入reset
ant design 中 form表单
momentjs 和 dayjs
await 成功才会往下走;使用ref获取表单实例,拿到表单对象;form表单验证
给form设置ref
3.关于请求
向服务器发送数据请求失败
- 请求失败:向服务器发送请求,服务器没有响应对应的信息HTTP状态码不是2开始的,或者压根就没有返回任何的信息1
- 服务器返回信息了状态码也是200] ,但是返回的内容不是我们想要 周味天 网络层失败 && 业务层失败
基于post请求向服务器发送请求,需要基于请求主体把信息传递给服务器
普通对象 >变为"Tobject Object]”字符串传递给服务器 [不对的]
Axios库对其做了处理,我们写的是普通对象,Axios内部会默认把其变为JSON字符串,传递给服务器!!
格式要求:
- 字符串 + json字符串 application/json
'{x:10,name:”珠峰"}'
+ urlencoded格式字符串 application/www-urlencoded
"X-10&name=珠峰"
+普通字符串 text/plain
- FormData对象「用于文件上传」 multipart/form-data
let im new FormData();
fm.append(‘file', file)
切换标签发送请求
请求数据
try catch后面的代码,相当于finally(与下图setTimeout无关,setTimeout用来模拟接口时间让loading效果展示出来)
在真实项目中,继承React.Component或者React.PureComponent差别不大
4.Task.jsx 完整代码
import React from "react";
import './Task.less';
import { Button, DatePicker, Form, Input, Modal, Popconfirm, Table, Tag, message } from 'antd';
import { getTaskList, addTask, removeTask, completeTask } from '@/api';
import { flushSync } from 'react-dom';
// 日期补0方法
const zero = function zero(str) {
str = String(str)
return str.length < 2 ? '0' + str : str
}
// 对日期处理的方法
const formatTime = function formatTime(time) {
let arr = time.match(/\d+/g),
[, month, day, hours = '00', minutes = '00'] = arr;
return `${zero(month)}-${zero(day)} ${zero(hours)}:${zero(minutes)}`
}
class Task extends React.PureComponent {
// 定义表格列的数据
columns = [
{
title: '编号',
dataIndex: 'id',
id: '1',
align: 'center',
width: '8%'
},
{
title: '任务描述',
dataIndex: 'task',
id: '2',
ellipsis: true,
width: '50%'
},
{
title: '状态',
dataIndex: 'state',
id: '3',
align: 'center',
width: '10%',
render: (text, record, index) => {
let res
switch (text) {
case 1:
res = '未完成'
break;
case 2:
res = '已完成'
break;
default:
res = ''
break;
}
return res
}
},
{
title: '完成时间',
dataIndex: 'time',
id: '4',
align: 'center',
width: '15%',
render: (_, record) => {
let { state, time, complete } = record
if (+state === 2) time = complete
return formatTime(time)
}
},
{
title: '操作',
render: (_, record) => {
const { id, state } = record
return <>
<Popconfirm title="您确定要删除吗" onConfirm={this.handleDelete.bind(null, id)}>
<Button type="link">删除</Button>
</Popconfirm>
{ +state !== 2 ? <Popconfirm title="您确定要完成吗" onConfirm={this.handleUpdate.bind(null, id)}>
<Button type="link">完成</Button>
</Popconfirm> : null }
</>
},
},
]
// 初始组件的状态
state = {
tableData: [
{
id: '1',
task: '今天天气很不错,今夜阳光灿烂,今天天气很不错,今夜阳光灿烂,今天天气很不错,今夜阳光灿烂,今天天气很不错,今夜阳光灿烂,今天天气很不错,今夜阳光灿烂',
time: '1999-01-11 01:01:45',
complete: '2023-05-12 19:10:32',
address: '西湖区湖底公园1号',
state: 1,
},
{
id: '2',
task: 'react知识哦',
time: '1999-01-101 01:01:45',
complete: '2023-05-12 19:38:57',
address: '西湖区湖底公园1号',
state: 2
},
],
tableLoading: false,
modalVisible: false,
confirmLoading: false,
selectedIndex: 0
}
// 对话框和表单处理
closeModal = () => {
this.setState({
modalVisible: false,
confirmLoading: false
})
this.formIns.resetFields()
}
// 提交
submit = async () => {
try {
// 表单校验 await 这里成功才会往下走,失败就不走了
await this.formIns.validateFields()
message.success('表单校验通过')
let { task, time } = this.formIns.getFieldsValue()
time = time.format('YYY-MM-DD HH:mm:ss')
console.log(126, task, time)
this.setState({ confirmLoading: true })
// 向服务器发送请求
let { code } = await addTask(task, time)
if (+code !== 0) {
message.error('很遗憾,当前操作失败,请稍后重试')
} else {
this.closeModal() // 关闭弹窗,取消loading,清除表单数据
this.queryData()
message.success('恭喜您,当前操作成功!')
}
} catch (_) {}
this.setState({ confirmLoading: false })
}
// 删除
handleDelete = async (id) => {
try {
let { code } = await removeTask(id)
if (+code !== 0) {
message.error('很遗憾,当前操作失败,请稍后重试')
} else {
this.queryData()
message.success('删除成功!')
}
} catch (_) {}
}
// 完成
handleUpdate = async (id) => {
try {
let { code } = await completeTask(id)
if (+code !== 0) {
message.error('很遗憾,当前操作失败,请稍后重试')
} else {
this.queryData()
message.success('更新成功!')
}
} catch (_) {}
}
// 切换全部/已完成/未完成标签
changeIndex = (index) => {
console.log(index)
if (this.state.selectedIndex === index) return
// this.setState({ selectedIndex: index })
// // 由于setState是异步,这里拿不到最新的selectedIndex
// this.queryData()
// // 方法一 在setState回调中取拿数据
// this.setState({ selectedIndex: index }, () => {
// this.queryData()
// })
// 方法二 flushSync()
// flushSync(this.setState({ selectedIndex: index }))
// this.queryData()
// 或者这样写
this.setState({ selectedIndex: index })
flushSync()
this.queryData()
}
// 从服务器获取指定状态的任务
queryData = async () => {
let { selectedIndex } = this.state
try {
this.setState({ tableLoading: true })
let { code, list } = await getTaskList(selectedIndex)
if (+code !== 0) list = [];
setTimeout(() => {
this.setState({ tableData: list })
}, 3000);
} catch (_) {}
// 此处相当于finally
setTimeout(() => {
this.setState({ tableLoading: false })
}, 3000);
}
// 周期函数
componentDidMount () {
// 第一次渲染完成后立即发送请求
this.queryData()
// tableLoading初始值就是false,在这里再改成false,还是会触发渲染,解决办法可以把class Task extends React.Component改成继承React.PureComponent,因为PureComponent中自动加了shouldComponentUpdate,对新老状态做了浅比较,缺点是 像这种操作,如果list中的数据没有变,执行this.setState({ tableData: list })还是会渲染,因为这里每次的list是引用 类型,地址是不一样的,最优方案是自己去做深比较
setTimeout(() => {
this.setState({ tableLoading: false })
}, 5000);
}
render () {
console.log('render')
let { tableData, tableLoading, modalVisible, confirmLoading, selectedIndex } = this.state
return <div className="task-box">
{/* 头部 */}
<div className="header">
<h2 className="title">Task OA管理系统</h2>
<Button type="primary" onClick={() => {
this.setState({
modalVisible: true
})
}}>新增任务</Button>
</div>
{/* 标签 */}
<div className="tag-box">
{['全部', '未完成', '已完成'].map((item, index) => {
return <Tag key={index} color={selectedIndex === index ? '#1677ff' : ''}
onClick={this.changeIndex.bind(null, index)}
>{item}</Tag>
})}
{/* <Tag color="#1677ff">全部</Tag>
<Tag>未完成</Tag>
<Tag>已完成</Tag> */}
</div>
{/* 表格 */}
<Table
dataSource={tableData}
columns={this.columns}
loading={tableLoading}
rowKey="id" />
{/* 对话框 & 表单 */}
<Modal open={modalVisible} title="新增任务弹窗" confirmLoading={confirmLoading} onOk={this.submit} onCancel={this.closeModal}>
<Form ref={ x => this.formIns = x } initialValues={{ task: '', time: '' }} layout="vertical"
style={{
maxWidth: 600,
}}>
<Form.Item name="task" label="任务描述" validateTrigger="onBlur" rules={[
{ required: true, message: '任务描述必填' },
{ min: 6, message: '至少输入6位' }
]}>
<Input.TextArea rows={4} allowClear />
</Form.Item>
<Form.Item name="time" label="完成时间" validateTrigger="onBlur" rules={[
{ required: true }
]}>
<DatePicker showTime format="YYYY/MM/DD HH:mm:ss" />
</Form.Item>
</Form>
</Modal>
</div>
}
}
export default Task;