35 Commits
Author SHA1 Message Date
Dawn1Ocean d74cbcb229 feat: add edit dutytable page 2024-09-19 13:59:56 +08:00
Dawn1Ocean f023e4b64a feat: dutytable page 2024-09-18 13:58:08 +08:00
Dawn1Ocean 5158fd19d9 chore: add avatar in About 2024-09-18 10:35:04 +08:00
Dawn1Ocean 1caeba1225 Merge branch 'mgy' of https://git.zjueva.net/Dawn_Ocean/EVA-Tea into mgy 2024-05-09 14:56:56 +08:00
Dawn1Ocean 44ce17d43d change doc & mock 2024-05-09 14:54:21 +08:00
Dawn1Ocean 31fca48feb feat: required check in dutyinfo; change service in dutyinfo 2024-05-08 19:01:58 +08:00
Dawn1Ocean 8f062b0e8c add dutyinfo page 2024-05-07 19:49:48 +08:00
Dawn1Ocean 1ff36f366d move week&shift process to src/utils/common 2024-05-07 12:21:51 +08:00
Dawn1Ocean edcd5dcbb7 add askleave page 2024-05-07 12:03:00 +08:00
Dawn1Ocean dd8c7e0b92 move report & conclusion from admin to leader 2024-05-07 11:26:14 +08:00
Dawn1Ocean 56452da055 add sideStaff display in about page 2024-05-07 11:14:28 +08:00
Dawn1Ocean 5fa85f7b77 fix: unstopping request bug in useEffect & useLayoutEffect 2024-04-21 23:02:18 +08:00
Dawn1Ocean a39cef6312 fix: change dutyinfo api 2024-04-21 22:59:19 +08:00
Dawn_Ocean 9fc19a3ebd refactor: request in DutyInfo & Welcome 2024-04-20 21:57:50 +08:00
Dawn1Ocean 311d67a1b1 feat: dutyinfo service 2024-04-19 19:45:58 +08:00
Dawn1Ocean 9bf2ef67be fix: api.md & mock 2024-04-19 14:24:04 +08:00
Dawn1Ocean 19a1c407f2 feat: about page & component 2024-04-18 16:20:01 +08:00
Dawn1Ocean de5150b385 refactor: remove redundant files 2024-04-17 19:43:45 +08:00
Dawn1Ocean f5c6f75779 feat: stats service in index page 2024-04-16 20:47:54 +08:00
Dawn1Ocean 0ea7eff03d temp 2024-04-16 19:30:44 +08:00
Dawn1Ocean a6d435a22f fix: logs table layout 2024-04-16 12:11:42 +08:00
Dawn1Ocean 136acf2731 fix: conclusion table layout 2024-04-16 12:09:52 +08:00
Dawn1Ocean d6a42f6087 move conclusion to admin; add protable in conclusion 2024-04-16 11:51:17 +08:00
Dawn1Ocean 2731f5560f fix welcome layout 2024-04-07 13:19:01 +08:00
Dawn1Ocean 0604a3dfbf fix welcome layout 2024-04-07 13:18:32 +08:00
Dawn1Ocean 3b52792917 fix: type ref bug in tiny.ring 2024-04-06 17:00:51 +08:00
Dawn1Ocean 29704a6197 fix: welcome page layout 2024-04-04 04:09:17 +08:00
Dawn1Ocean 29fa1341d8 feat: chart in welcome page 2024-04-04 03:12:28 +08:00
Dawn1Ocean 58719b709b change /admin/reportlist api 2024-04-03 16:38:07 +08:00
Dawn1Ocean 01234a6042 add procard in welcome 2024-04-02 19:21:12 +08:00
Dawn1Ocean 54f7fe53a5 feat: prolist in /admin/report 2024-04-02 15:53:26 +08:00
Dawn1Ocean e71abc0c0b add mock: reportlist 2024-04-02 14:59:06 +08:00
Dawn1Ocean 2cb4684130 add log component: prolist 2024-04-02 02:55:09 +08:00
Dawn1Ocean 0d1665f609 remove redundant services 2024-03-31 22:32:12 +08:00
Dawn1Ocean da62939b0d change README.md 2024-03-31 22:15:12 +08:00
57 changed files with 2503 additions and 24387 deletions
+40 -37
View File
@@ -1,57 +1,60 @@
# Ant Design Pro # Notify Admin
This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use. ## 目标
## Environment Prepare 1. 微信小程序的信息管理页面
2. 人资部后台(长期)
Install `node_modules`: ## 功能
```bash ### 主席团管理页面
npm install
```
or 1. 值班表导入、更新
1. 值班表渲染
2. 值班表在线更改、上传后端
3. 值班表导出
2. 成员权限管理
1. 从 Auth 获取成员职位、部门
2. 主席团成员:可更改所有信息
3. 当班所有成员:可更改值班人数,可加入 Oreo,可更改状态
4. 所有成员:可认领
3. 特殊维修时间(如学园维修)、值班信息更新
4. 日志
1. 记录每一次操作、操作者
5. 自动排班功能(Enhancement)
```bash ### 值班组长管理页面
yarn
```
## Provided Scripts ps. 是微信小程序中小组管理页面的pc端替代
Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test. 1. 值班组长上传本班相关信息
2. 值班组长上传值班总结(Enhancement)
Scripts provided in `package.json`. It's safe to modify or add additional script: ## UI
### Start project ### 仪表盘
```bash 1. 个人信息、职位
npm start 2. 新系统中(未加入Oreo/预填写工单总数)统计
```
### Build project ### 主席团管理页面
```bash #### 值班表管理
npm run build
```
### Check code style 1. 值班表渲染
2. 值班表在线更改、上传后端
1. 特殊时间值班表不上传
3. 值班表导出
```bash #### 特殊值班时间
npm run lint
```
You can also use script to auto fix some lint error: 1. (switch) 是否启用平常值班时间
2. 若不在平常值班时间,选择录入不值班原因/特殊值班时间
```bash ### 值班组长管理页面(暂空)
npm run lint:fix
```
### Test code 1. (Enhancement) 值班总结上传
```bash ### 日志(所有人可见)
npm test
```
## More 1. 查询功能
You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).
-78
View File
@@ -233,84 +233,6 @@
} }
} }
}, },
"RuleListItem": {
"type": "object",
"properties": {
"key": {
"type": "integer",
"format": "int32"
},
"disabled": {
"type": "boolean"
},
"href": {
"type": "string"
},
"avatar": {
"type": "string"
},
"name": {
"type": "string"
},
"owner": {
"type": "string"
},
"desc": {
"type": "string"
},
"callNo": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "integer",
"format": "int32"
},
"updatedAt": {
"type": "string",
"format": "datetime"
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"progress": {
"type": "integer",
"format": "int32"
}
}
},
"RuleList": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/RuleListItem"
}
},
"total": {
"type": "integer",
"description": "列表的内容总数",
"format": "int32"
},
"success": {
"type": "boolean"
}
}
},
"FakeCaptcha": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"status": {
"type": "string"
}
}
},
"LoginParams": { "LoginParams": {
"type": "object", "type": "object",
"properties": { "properties": {
+18 -4
View File
@@ -13,19 +13,33 @@ export default [
routes: [ routes: [
{ path: '/admin', redirect: '/admin/dutyinfo' }, { path: '/admin', redirect: '/admin/dutyinfo' },
{ path: '/admin/dutyinfo', name: '值班信息', component: './Admin/DutyInfo' }, { path: '/admin/dutyinfo', name: '值班信息', component: './Admin/DutyInfo' },
{ path: '/admin/dutytable', name: '值班表管理', component: './Admin/DutyTable' }, {
path: '/admin/dutytable',
name: '值班表管理',
component: './Admin/DutyTable',
},
{ path: '/admin/logs', name: '日志', component: './Admin/Logs' },
], ],
}, },
{ {
path: '/leader', path: '/leader',
name: '值班管理', name: '值班管理',
icon: 'team', icon: 'team',
access: 'canLeader',
routes: [ routes: [
{ path: '/leader', redirect: '/admin/dutyinfo' }, { path: '/leader', redirect: '/leader/conclusion' },
{ path: '/leader/conclusion', name: '值班总结', component: './Leader/Conclusion' }, { path: '/leader/conclusion', name: '值班总结管理', component: './Leader/Conclusion' },
{ path: '/leader/report', name: '反馈信息', component: './Leader/Report' },
{ path: '/leader/askleave', name: '请假管理', component: './Leader/AskLeave' },
], ],
}, },
{ name: '日志', icon: 'clockCircle', path: '/logs', component: './Logs' }, {
path: '/admin/dutytable/edit',
component: './Admin/DutyTable/EditTable',
menu: {
hideInMenu: true,
},
},
{ name: '关于我们', icon: 'thunderbolt', path: '/about', component: './About' }, { name: '关于我们', icon: 'thunderbolt', path: '/about', component: './About' },
{ path: '/', redirect: '/welcome' }, { path: '/', redirect: '/welcome' },
{ path: '*', layout: false, component: './404' }, { path: '*', layout: false, component: './404' },
+293 -43
View File
@@ -24,7 +24,79 @@
} }
``` ```
### `GET`请求默认格式(若无特殊说明) ### `GET` 请求默认格式(若无特殊说明)
```json
{
"token": "token_test"
}
```
## 登陆
### 账号密码登陆 `POST /login`
request:
```json
{
"stuid": "3220114514",
"passwd": "__passwd_test__"
}
```
data:
.role
-1 => 未定
0 => 干事
1 => 部长
2 => 会长
3 => 副会长
4 => 技术指导
5 => 已退休
6 => 管理员
7 => 副部长
8 => 总监
.department
-1 => 未定
0 => 电器部
1 => 电脑部
2 => 人资部
3 => 文宣部
4 => 财外部
```json
{
"token": "token_test",
"name": "亦可",
"avatar": "https://....png",
"role": 6,
"department": 1,
"access": "admin" // "admin" => 主席团成员 "leader" => 值班组长 "member" => 普通成员
}
```
### 登出 `POST /logout`
request:
```json ```json
{ {
@@ -34,7 +106,7 @@
## 主页面 ## 主页面
### 统计数据 `GET /admin/stats` ### 统计数据 `GET /stats`
data: data:
@@ -42,29 +114,114 @@ data:
{ {
"success": true, "success": true,
"data": { "data": {
"notInOreo": 114, "todayPT": 114, // 今日预填写工单总数
"total": 514 "totalPT": 514, // 所有预填写工单
"oreo": 93, // 加入 Oreo 工单数
"report": 8, // 今日反馈数
"allSuccess": 1919, // 所有成功工单(包括 Oreo 内)
"all": 2024 // 所有工单(包括 Oreo 内)
} }
} }
``` ```
### 当前值班信息 `GET /admin/duty/current` ## 主席团页面
### 值班信息管理
#### 当前值班信息 `GET /admin/duty/current`
##### 正常值班
data: data:
```json ```json
{ {
"isInDuty": true, "isNormalDuty": true, // 判断是否是正常值班时间
"currentDuty": "2", "currentDuty": "1", // 判断是否正在值班 / 值哪一班 "1" | "2" | "3" => 正在值班,"off" => 暂停值班, "0" => 值班下班
"inDutyCnt": 3, "inDutyCnt": 3,
"otherDutyTime": "", "otherDutyTime": [], // 正常值班时为空
"offDutyReason": "", "offDutyReason": "",
"place": "204", "place": "204",
"dutyRecoverTime": "" "dutyRecoverTime": ""
} }
``` ```
## 主席团页面 ##### 其他值班
data:
```json
{
"isNormalDuty": false, // 判断是否是正常值班时间
"currentDuty": "others", // 判断是否正在值班 / 值哪一班 "others" => 正在值班,"off" => 暂停值班, "0" => 值班下班
"inDutyCnt": 3,
"otherDutyTime": [
{
"name": "第一班",
"place": "蓝田",
"range": ["2024-03-07T19:52:48.523303", "2024-03-07T19:52:48.523303"]
},
{
"name": "第二班",
"place": "蓝田",
"range": ["2024-03-07T19:52:48.523303", "2024-03-07T19:52:48.523303"]
},
...
],
"offDutyReason": "",
"place": "204",
"dutyRecoverTime": ""
}
```
#### 更新当前值班情况 `POST /admin/duty/update`
无关项不返回,后端手动校验
新增字段:dutyStatus "normal" => 正常值班 "others" => 特殊值班 "pause" => 暂停值班
request:
##### 正常值班
```json
{
"dutyStatus": "normal",
"inDutyCnt": 3,
},
```
##### 特殊值班
```json
{
"dutyStatus": "others",
"inDutyCnt": 3,
"otherDutyTime": [
{
"name": "第一班",
"place": "蓝田",
"range": ["2024-03-07T19:52:48.523303", "2024-03-07T19:52:48.523303"]
},
{
"name": "第二班",
"place": "蓝田",
"range": ["2024-03-07T19:52:48.523303", "2024-03-07T19:52:48.523303"]
},
...
],
},
```
##### 暂停值班
```json
{
"dutyStatus": "pause",
"offDutyReason": "期中考试周",
"dutyRecoverTime": "2024-03-07T19:52:48.523303",
},
```
### 值班表管理 ### 值班表管理
@@ -93,7 +250,6 @@ request:
} }
``` ```
#### 更新值班表 `POST /admin/sheet/update` #### 更新值班表 `POST /admin/sheet/update`
request: request:
@@ -180,51 +336,67 @@ data:
} }
``` ```
### 特殊值班 ### 反馈管理
#### 获取当前值班情况 `GET /admin/duty/current` #### 获取反馈列表 `GET /admin/reportlist`
data:
```json
{
"isInDuty": true,
"currentDuty": "2",
"inDutyCnt": 3,
"otherDutyTime": "",
"offDutyReason": "",
"place": "",
"dutyRecoverTime": "",
},
```
#### 更新当前值班情况 `POST /admin/duty/update`
无关项返回空字符串,后端手动校验
request: request:
```json ```json
{ {
"isInDuty": true, "current": 1,
"currentDuty": "2", "pageSize": 10,
"inDutyCnt": 3, "name?": "西西弗", // 如果不筛选则不出现在 request-body 内
"otherDutyTime": "", "phone?": "18888888888" // 如果不筛选则不出现在 request-body 内
"offDutyReason": "", }
"place": "204",
"dutyRecoverTime": "",
},
``` ```
## 日志页面
### 获取日志列表 `GET /admin/loglist`
data: data:
```json ```json
{ {
"page": 1,
"total": 24,
"success": true,
"data": [
{
"name": "晓洋",
"phone": "13333333333",
"time": "2024-03-07T17:52:48.523303",
"text": "你们为什么修不好天选3的PD充电?"
}
// ...
]
}
```
### 日志页面
#### 获取日志列表 `GET /admin/loglist`
request:
```json
{
"current": 1,
"pageSize": 10,
"op?": "晓洋", // 如果不筛选则不出现在 request-body 内
"operation": "all" | "add" | "delete" | "change", // all 之外的选项用于筛选
"target": "all" | "dutyTable" | "dutyInfo" | "conclusion" // all 之外的选项用于筛选
}
```
data:
```json
{
"page": 1,
"total": 24,
"success": true,
"data": [
{
"op": "宇航员", "op": "宇航员",
"avatar": "https://...",
"time": "2024-03-07T19:52:48.523303", "time": "2024-03-07T19:52:48.523303",
"operation": 2, "operation": 2,
"target": 1, "target": 1,
@@ -236,7 +408,85 @@ data:
"id": 514, "id": 514,
"info": "何君琳" "info": "何君琳"
} }
}, }
// ...
]
}
``` ```
前端显示的信息:"{op} 于 {time} {operation} 了 {target}:{prev.info} -> {curr.info}" 前端显示的信息:"{op} 于 {time} {operation} 了 {target}:{prev.info} -> {curr.info}"
### 值班总结管理页面
#### 获取值班总结列表 `GET /admin/conclusionlist`
request:
```json
{
"current": 1,
"pageSize": 10,
"op?": "晓洋", // 如果不筛选则不出现在 request-body 内
"week": "all" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun", // all 之外的选项用于筛选
"shift": "all" | "first" | "second" | "third" // all 之外的选项用于筛选
}
```
data:
```json
{
"page": 1,
"total": 24,
"success": true,
"data": [
{
"op": "宇航员",
"avatar": "https://...",
"time": "2024-03-07T19:52:48.523303",
"week": "2",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
}
// ...
]
}
```
### 请假管理页面
#### 获取请假列表 `GET /leader/askleavelist`
request:
```json
{
"current": 1,
"pageSize": 10,
"member?": "晓洋", // 如果不筛选则不出现在 request-body 内
"week": "all" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun", // all 之外的选项用于筛选
"shift": "all" | "first" | "second" | "third" // all 之外的选项用于筛选
}
```
data:
```json
{
"page": 1,
"total": 24,
"success": true,
"data": [
{
"member": "宇航员",
"avatar": "https://...",
"time": "2024-03-07T19:52:48.523303",
"week": "2",
"shift": "3",
"reason": "家庭原因",
"substitute": "晓洋" // 未找人代班则为空
}
// ...
]
}
```
+92
View File
@@ -0,0 +1,92 @@
[
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:48.523303",
"week": "2",
"shift": "3",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:49.523303",
"week": "2",
"shift": "1",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:50.523303",
"week": "2",
"shift": "2",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "晓洋",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:51.523303",
"week": "7",
"shift": "3",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:52.523303",
"week": "7",
"shift": "1",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:53.523303",
"week": "2",
"shift": "3",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:54.523303",
"week": "2",
"shift": "3",
"reason": "英语六级口试",
"substitute": ""
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:55.523303",
"week": "2",
"shift": "3",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:56.523303",
"week": "2",
"shift": "3",
"reason": "英语六级口试",
"substitute": "晓洋"
},
{
"member": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:57.523303",
"week": "2",
"shift": "3",
"reason": "英语六级口试",
"substitute": "晓洋"
}
]
+82
View File
@@ -0,0 +1,82 @@
[
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:48.523303",
"week": "2",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:49.523303",
"week": "2",
"shift": "1",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:50.523303",
"week": "2",
"shift": "2",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "晓洋",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:51.523303",
"week": "7",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:52.523303",
"week": "7",
"shift": "1",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:53.523303",
"week": "2",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:54.523303",
"week": "2",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:55.523303",
"week": "2",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:56.523303",
"week": "2",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:57.523303",
"week": "2",
"shift": "3",
"detail": "收到电脑 n 台,维修成功 m 台,已取回 x 台,有 y 台正在维修交接给下一班"
}
]
+152
View File
@@ -0,0 +1,152 @@
[
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:48.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:49.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:50.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "晓洋",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:51.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:52.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:53.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:54.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:55.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:56.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
},
{
"op": "宇航员",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"time": "2024-03-07T19:52:57.523303",
"operation": 2,
"target": 1,
"prev": {
"id": 114,
"info": "马嘉祺"
},
"curr": {
"id": 514,
"info": "何君琳"
}
}
]
+72
View File
@@ -0,0 +1,72 @@
[
{
"name": "晓洋",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333333",
"time": "2024-03-07T17:52:48.523303",
"text": "你们为什么修不好天选3的PD充电?"
},
{
"name": "晓洋1",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333334",
"time": "2024-03-07T17:53:48.523303",
"text": "你们为什么修不好天选4的PD充电?"
},
{
"name": "晓洋2",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333335",
"time": "2024-03-07T17:54:48.523303",
"text": "你们为什么修不好天选5的PD充电?"
},
{
"name": "晓洋3",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333336",
"time": "2024-03-07T17:55:48.523303",
"text": "你们为什么修不好天选6的PD充电?"
},
{
"name": "晓洋4",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333337",
"time": "2024-03-07T17:56:48.523303",
"text": "你们为什么修不好天选7的PD充电?我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了我要气死了"
},
{
"name": "晓洋5",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333338",
"time": "2024-03-07T17:57:48.523303",
"text": "你们为什么修不好天选8的PD充电?"
},
{
"name": "晓洋6",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333339",
"time": "2024-03-07T17:58:48.523303",
"text": "你们为什么修不好天选9的PD充电?"
},
{
"name": "晓洋7",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333330",
"time": "2024-03-07T17:59:48.523303",
"text": "你们为什么修不好天选0的PD充电?"
},
{
"name": "晓洋8",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333331",
"time": "2024-03-07T18:52:48.523303",
"text": "你们为什么修不好天选1的PD充电?"
},
{
"name": "晓洋9",
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png",
"phone": "13333333332",
"time": "2024-03-07T19:52:48.523303",
"text": "你们为什么修不好天选2的PD充电?"
}
]
+72 -42
View File
@@ -1,12 +1,20 @@
import sheet from './sheet.json' import askleavelist from './askleavelist.json';
import sheetlist from './sheetlist.json' import conclusionlist from './conclusionlist.json';
import loglist from './loglist.json';
import reportlist from './reportlist.json';
import sheet from './sheet.json';
import sheetlist from './sheetlist.json';
module.exports = { module.exports = {
'GET /admin/stats': { 'GET /stats': {
success: true, success: true,
data: { data: {
notInOreo: 114, todayPT: 114, // 今日预填写工单总数
total: 514, totalPT: 514, // 所有预填写工单
oreo: 93, // 加入 Oreo 工单数
report: 8, // 今日反馈数
allSuccess: 1919, // 所有成功工单(包括 Oreo 内)
all: 2024, // 所有工单(包括 Oreo 内)
}, },
}, },
'POST /admin/sheet/update': { 'POST /admin/sheet/update': {
@@ -28,13 +36,31 @@ module.exports = {
'GET /admin/duty/current': { 'GET /admin/duty/current': {
success: true, success: true,
data: { data: {
isInDuty: true, isNormalDuty: true,
currentDuty: '2', currentDuty: 'off',
inDutyCnt: 3, inDutyCnt: 3,
otherDutyTime: '', otherDutyTime: [
offDutyReason: '', {
place: '204', id: 1,
dutyRecoverTime: '', title: '第一班',
place: 'fsaf',
range: ['2024-03-07T19:52:48.523303', '2024-03-07T19:52:48.523303'],
},
{
id: 2,
title: '第二班',
place: 'fsdhhv',
range: ['2024-03-07T19:52:48.523303', '2024-03-07T19:52:48.523303'],
},
{
id: 3,
title: '第三班',
place: 'fdshq',
range: ['2024-03-07T19:52:48.523303', '2024-03-07T19:52:48.523303'],
},
],
offDutyReason: '期中考试周',
dutyRecoverTime: '2024-03-07T19:52:48.523303',
}, },
}, },
'POST /admin/duty/update': { 'POST /admin/duty/update': {
@@ -42,48 +68,52 @@ module.exports = {
data: {}, data: {},
}, },
'GET /admin/loglist': { 'GET /admin/loglist': {
success: true,
page: 1,
total: 24,
data: loglist,
},
'GET /leader/reportlist': {
success: true,
page: 1,
total: 24,
data: reportlist,
},
'GET /leader/conclusionlist': {
success: true,
page: 1,
total: 24,
data: conclusionlist,
},
'GET /leader/askleavelist': {
success: true,
page: 1,
total: 24,
data: askleavelist,
},
'POST /login': {
success: true, success: true,
data: { data: {
op: '宇航员', token: 'token_test',
time: '2024-03-07T19:52:48.523303', name: '亦可',
operation: 2, avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
target: 1, role: 6,
prev: { department: 1,
id: 114, access: 'admin',
info: '马嘉祺',
},
curr: {
id: 514,
info: '何君琳',
}, },
}, },
'POST /logout': {
success: true,
data: {},
}, },
'GET /api/currentUser': { 'GET /api/currentUser': {
success: true,
data: { data: {
name: 'Serati Ma', name: 'Serati Ma',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
userid: '00000001', userid: '00000001',
email: 'antdesign@alipay.com',
signature: '海纳百川,有容乃大',
title: '交互专家',
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
tags: [ access: 'admin',
{ key: '0', label: '很有想法的' },
{ key: '1', label: '专注设计' },
{ key: '2', label: '辣~' },
{ key: '3', label: '大长腿' },
{ key: '4', label: '川妹子' },
{ key: '5', label: '海纳百川' },
],
notifyCount: 12,
unreadCount: 11,
country: 'China',
geographic: {
province: { label: '浙江省', key: '330000' },
city: { label: '杭州市', key: '330100' },
},
address: '西湖区工专路 77 号',
phone: '0752-268888888',
}, },
}, },
'POST /api/login/outLogin': { data: {}, success: true }, 'POST /api/login/outLogin': { data: {}, success: true },
-5
View File
@@ -1,5 +0,0 @@
export default {
'/api/auth_routes': {
'/form/advanced-form': { authority: ['admin', 'user'] },
},
};
+4 -6
View File
@@ -1,10 +1,9 @@
{ [
"current": 514,
"list": [
{ {
"name": "2024 春学期值班表", "name": "2024 春学期值班表",
"createdTime": "2024-03-07T19:52:48.523303", "createdTime": "2024-03-07T19:52:48.523303",
"id": 114, "id": 114,
"isSelect": true,
"sheet": [ "sheet": [
{ {
"weekday": 1, "weekday": 1,
@@ -132,6 +131,7 @@
"name": "2024 秋学期值班表", "name": "2024 秋学期值班表",
"createdTime": "2024-03-07T11:45:14.523303", "createdTime": "2024-03-07T11:45:14.523303",
"id": 514, "id": 514,
"isSelect": false,
"sheet": [ "sheet": [
{ {
"weekday": 1, "weekday": 1,
@@ -255,6 +255,4 @@
} }
] ]
} }
] ]
}
-68
View File
@@ -42,76 +42,11 @@ export default {
name: 'Serati Ma', name: 'Serati Ma',
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
userid: '00000001', userid: '00000001',
email: 'antdesign@alipay.com',
signature: '海纳百川,有容乃大',
title: '交互专家',
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
tags: [
{
key: '0',
label: '很有想法的',
},
{
key: '1',
label: '专注设计',
},
{
key: '2',
label: '辣~',
},
{
key: '3',
label: '大长腿',
},
{
key: '4',
label: '川妹子',
},
{
key: '5',
label: '海纳百川',
},
],
notifyCount: 12,
unreadCount: 11,
country: 'China',
access: getAccess(), access: getAccess(),
geographic: {
province: {
label: '浙江省',
key: '330000',
},
city: {
label: '杭州市',
key: '330100',
},
},
address: '西湖区工专路 77 号',
phone: '0752-268888888',
}, },
}); });
}, },
// GET POST 可省略
'GET /api/users': [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
],
'POST /api/login/account': async (req: Request, res: Response) => { 'POST /api/login/account': async (req: Request, res: Response) => {
const { password, username, type } = req.body; const { password, username, type } = req.body;
await waitTime(2000); await waitTime(2000);
@@ -154,9 +89,6 @@ export default {
access = ''; access = '';
res.send({ data: {}, success: true }); res.send({ data: {}, success: true });
}, },
'POST /api/register': (req: Request, res: Response) => {
res.send({ status: 'ok', currentAuthority: 'user', success: true });
},
'GET /api/500': (req: Request, res: Response) => { 'GET /api/500': (req: Request, res: Response) => {
res.status(500).send({ res.status(500).send({
timestamp: 1513932555104, timestamp: 1513932555104,
+24
View File
@@ -0,0 +1,24 @@
import { useCallback, useState } from 'react';
export default () => {
const [user, setUser] = useState({});
const [token, setToken] = useState('');
const setUserData = useCallback(
(name: string, avatar: string, group: number, title: number, access: string) => {
setUser({
name: name,
avatar: avatar,
group: group,
title: title,
access: access,
});
},
[],
);
const setTokenStr = useCallback((token: string) => {
setToken(token);
}, []);
return { user, setUserData, token, setTokenStr };
};
+1
View File
@@ -46,6 +46,7 @@
"not ie <= 10" "not ie <= 10"
], ],
"dependencies": { "dependencies": {
"@ant-design/charts": "^2.0.3",
"@ant-design/icons": "^4.8.1", "@ant-design/icons": "^4.8.1",
"@ant-design/pro-components": "^2.7.0", "@ant-design/pro-components": "^2.7.0",
"@umijs/route-utils": "^2.2.2", "@umijs/route-utils": "^2.2.2",
-21929
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -1,3 +1,5 @@
import { API } from './services/typings';
/** /**
* @see https://umijs.org/docs/max/access#access * @see https://umijs.org/docs/max/access#access
* */ * */
@@ -5,5 +7,6 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
const { currentUser } = initialState ?? {}; const { currentUser } = initialState ?? {};
return { return {
canAdmin: currentUser && currentUser.access === 'admin', canAdmin: currentUser && currentUser.access === 'admin',
canLeader: currentUser && (currentUser.access === 'leader' || currentUser.access === 'admin'),
}; };
} }
+2 -1
View File
@@ -1,5 +1,5 @@
import { AvatarDropdown, AvatarName, PageFooter } from '@/components'; import { AvatarDropdown, AvatarName, PageFooter } from '@/components';
import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; import { currentUser as queryCurrentUser } from '@/services/api';
import { LinkOutlined } from '@ant-design/icons'; import { LinkOutlined } from '@ant-design/icons';
import type { Settings as LayoutSettings } from '@ant-design/pro-components'; import type { Settings as LayoutSettings } from '@ant-design/pro-components';
import { SettingDrawer } from '@ant-design/pro-components'; import { SettingDrawer } from '@ant-design/pro-components';
@@ -7,6 +7,7 @@ import type { RunTimeLayoutConfig } from '@umijs/max';
import { Link, history } from '@umijs/max'; import { Link, history } from '@umijs/max';
import defaultSettings from '../config/defaultSettings'; import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig'; import { errorConfig } from './requestErrorConfig';
import { API } from './services/typings';
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login'; const loginPath = '/user/login';
+33
View File
@@ -0,0 +1,33 @@
import { Flex, Image, Typography } from 'antd';
const { Text, Link } = Typography;
const Brief: React.FC<{
avatar: string;
name: string;
nickname: string;
profile: string;
link?: string;
linktext?: string;
isSideStaff?: boolean;
}> = ({ avatar, name, nickname, profile, link, linktext, isSideStaff }) => {
return (
<div style={{ marginTop: '5vh', marginBottom: '5vh' }}>
<Flex gap="middle" align="center" justify="center" vertical={!isSideStaff}>
<Image width={isSideStaff ? 75 : 200} src={avatar} preview={false} />
<Flex gap="small" align="center" justify="center" vertical>
<Text style={{ fontSize: isSideStaff ? '18px' : '24px' }}>{name}</Text>
<Flex gap="small" align="center" justify="center" vertical={false}>
<Text type={'secondary'}>{nickname}</Text>
<Link href={link} target="_blank">
{linktext}
</Link>
</Flex>
<Text type={'secondary'}>{profile}</Text>
</Flex>
</Flex>
</div>
);
};
export default Brief;
+1 -1
View File
@@ -21,7 +21,7 @@ const PageFooter: React.FC = () => {
{ {
key: 'EVA Tea', key: 'EVA Tea',
title: 'EVA Tea', title: 'EVA Tea',
href: 'https://pro.ant.design', href: 'https://a.zjueva.net',
blankTarget: true, blankTarget: true,
}, },
]} ]}
@@ -1,4 +1,4 @@
import { outLogin } from '@/services/ant-design-pro/api'; import { outLogin } from '@/services/api';
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'; import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
import { history, useModel } from '@umijs/max'; import { history, useModel } from '@umijs/max';
import { Spin } from 'antd'; import { Spin } from 'antd';
+73 -5
View File
@@ -1,13 +1,81 @@
import { Todo } from '@/components'; import Brief from '@/components/Brief';
import { PageContainer } from '@ant-design/pro-components'; import { PageContainer, ProCard } from '@ant-design/pro-components';
import { Card } from 'antd'; import { Card } from 'antd';
import RcResizeObserver from 'rc-resize-observer';
import { useState } from 'react';
import staffs, { Staff } from './staff';
const Logs: React.FC = () => { const Logs: React.FC = () => {
const [responsive, setResponsive] = useState(false);
const sideMembers = staffs.sideStaffList.length;
const pushSideStaff = (sideStaff: JSX.Element[][], tempMembers: Staff[]) => {
sideStaff.push(
tempMembers.map((staff: Staff, idx) => (
<ProCard key={idx} style={{ whiteSpace: 'pre-line' }}>
<Brief
avatar={staff.avatar}
name={staff.name}
nickname={staff.nickname}
profile={staff.profile}
link={staff.link}
linktext={staff.linktext}
isSideStaff
/>
</ProCard>
)),
);
};
let sideStaff: JSX.Element[][] = [],
tempMembers: Staff[] = [], i = 0, j;
/// 当个数模 3 余 1 时,先放置 2 行,每行 2 个;
/// 当个数模 3 余 2 时,先放置 1 行,每行 2 个;
/// 其余按每行 3 个放置
for (j = 0; j < Math.round(sideMembers / 3); j++) {
tempMembers.push(staffs.sideStaffList[i++]);
tempMembers.push(staffs.sideStaffList[i++]);
if (j >= (3 - sideMembers % 3) % 3) {
tempMembers.push(staffs.sideStaffList[i++]);
}
pushSideStaff(sideStaff, tempMembers);
tempMembers = [];
}
const staffRenderer = (
<div>
<RcResizeObserver
key="resize-observer"
onResize={(offset) => {
setResponsive(offset.width < 596);
}}
>
<ProCard.Group direction={responsive ? 'column' : 'row'}>
{staffs.staffList.map((staff: Staff, idx) => (
<ProCard key={idx} style={{ whiteSpace: 'pre-line' }}>
<Brief
avatar={staff.avatar}
name={staff.name}
nickname={staff.nickname}
profile={staff.profile}
link={staff.link}
linktext={staff.linktext}
/>
</ProCard>
))}
</ProCard.Group>
{sideStaff.map((staffLine, idx) => (
<ProCard.Group direction={responsive ? 'column' : 'row'} key={idx}>
{staffLine}
</ProCard.Group>
))}
</RcResizeObserver>
</div>
);
return ( return (
<PageContainer> <PageContainer>
<Card> <Card>{staffRenderer}</Card>
<Todo />
</Card>
</PageContainer> </PageContainer>
); );
}; };
+44
View File
@@ -0,0 +1,44 @@
export interface Staff {
avatar: string;
name: string;
nickname: string;
profile: string;
link?: string;
linktext?: string;
}
const staffList: Staff[] = [
{
avatar: 'https://wiki.zjueva.net/images/1/10/%E5%A4%B4%E5%83%8F.jpg',
name: '马国洋',
nickname: 'Dawn_Ocean',
profile: 'Eta & Tea 总设计\n摸鱼与开发叠加态',
link: 'https://blog.deans.fun',
linktext: '我的博客',
},
{
avatar: 'https://static.fracher21.top/head.jpg',
name: '杨鸿奕',
nickname: 'Fracher',
profile: 'Ate 总设计, 项目合作贡献者\nReact 真是太有意思了',
},
];
const sideStaffList: Staff[] = [
{
avatar: 'https://wiki.zjueva.net/images/avatars/eva_wiki_27_l.png',
name: '亦可',
nickname: 'Echo',
profile: 'Eta 系统总运维',
link: 'https://wiki.zjueva.net/%E7%94%A8%E6%88%B7:%E4%BA%A6%E5%8F%AF',
linktext: '我的主页',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
name: '泰斯特',
nickname: 'Tester',
profile: 'Eta 系统总测试',
},
];
export default { staffList, sideStaffList };
-14
View File
@@ -1,14 +0,0 @@
import { Todo } from '@/components';
import { PageContainer } from '@ant-design/pro-components';
import { Card } from 'antd';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<Todo />
</Card>
</PageContainer>
);
};
export default Logs;
+218
View File
@@ -0,0 +1,218 @@
import { getDutyInfo } from '@/services/api';
import {
EditableProTable,
PageContainer,
ProColumns,
ProForm,
ProFormDateTimePicker,
ProFormDependency,
ProFormDigit,
ProFormRadio,
ProFormText,
} from '@ant-design/pro-components';
import { Card, message } from 'antd';
import { useEffect, useState } from 'react';
import request from 'umi-request';
import { Info } from './info';
type Shift = {
id: React.Key;
title: string;
place: string;
range: string[];
};
const columns: ProColumns<Shift>[] = [
{
title: '值班名称',
dataIndex: 'title',
width: '15%',
},
{
title: '值班地点',
dataIndex: 'place',
width: '15%',
},
{
title: '值班时间',
dataIndex: 'range',
valueType: 'dateTimeRange',
width: '40%',
},
{
title: '操作',
valueType: 'option',
width: '10%',
},
];
const DutyInfo: React.FC = () => {
const [dutyInfo, setDutyInfo] = useState<Info>();
const [loading, setLoading] = useState(true);
const [messageApi, contextHolder] = message.useMessage();
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => []);
useEffect(() => {
getDutyInfo()
.then((res) => {
const data = res.data;
console.log(data);
setDutyInfo({
dutyStatus:
data.currentDuty === 'off' ? 'pause' : data.isNormalDuty ? 'others' : 'normal',
inDutyCnt: data.inDutyCnt,
otherDutyTime: data.otherDutyTime,
offDutyReason: data.offDutyReason,
dutyRecoverTime: data.dutyRecoverTime,
});
setEditableRowKeys(data.otherDutyTime.map((item) => item.id));
setLoading(false);
})
.catch((err) => {
messageApi.open({
type: 'error',
content: '获取失败!' + err,
});
});
}, []);
return (
<PageContainer>
{contextHolder}
<Card>
{!loading && (
<ProForm<Info>
submitter={{
resetButtonProps: { style: { display: 'none' } },
submitButtonProps: {
style: { width: '31%' },
},
render: (_, doms) => {
return <div style={{ textAlign: 'center' }}>{doms}</div>;
},
}}
onFinish={async (values) => {
setLoading(true);
request('/admin/duty/update', {
method: 'POST',
data: values,
})
.then(() => {
setLoading(false);
message.success('提交成功');
})
.catch((err) => {
setLoading(false);
message.error('提交失败:' + err);
});
}}
initialValues={dutyInfo}
>
<div style={{ justifyContent: 'center', display: 'flex' }}>
<ProFormRadio.Group
name="dutyStatus"
label="值班状态"
options={[
{
label: '正常值班',
value: 'normal',
},
{
label: '特殊值班',
value: 'others',
},
{
label: '值班暂停',
value: 'pause',
},
]}
/>
</div>
<div style={{ justifyContent: 'center', display: 'flex' }}>
<ProFormDependency name={['dutyStatus']}>
{({ dutyStatus }) => {
if (dutyStatus === 'pause') {
return (
<div>
<ProFormText
width="md"
name="offDutyReason"
label="值班暂停原因"
placeholder="请输入暂停值班的原因"
rules={[{ required: true }]}
/>
<ProFormDateTimePicker
name="dutyRecoverTime"
width="md"
label="值班恢复时间"
rules={[{ required: true }]}
/>
</div>
);
}
return (
<ProFormDigit
label="当前值班人数"
name="inDutyCnt"
placeholder="请输入值班人数"
width="md"
min={0}
max={10}
rules={[{ required: true }]}
/>
);
}}
</ProFormDependency>
</div>
<ProFormDependency name={['dutyStatus']}>
{({ dutyStatus }) => {
if (dutyStatus === 'others') {
return (
<div style={{ justifyContent: 'center', display: 'flex' }}>
<ProForm.Item
label="其他值班班次"
name="dataSource"
initialValue={dutyInfo?.otherDutyTime}
trigger="onValuesChange"
style={{ width: '80%' }}
rules={[{ required: true }]}
>
<EditableProTable<Shift>
rowKey="id"
toolBarRender={false}
columns={columns}
recordCreatorProps={{
newRecordType: 'dataSource',
position: 'bottom',
creatorButtonText: '新建一个值班班次',
style: { width: '100%', margin: 'auto', marginTop: '2%' },
record: () => ({
id: Date.now(),
title: '',
place: '',
range: [],
}),
}}
editable={{
type: 'multiple',
editableKeys,
onChange: setEditableRowKeys,
actionRender: (row, _, dom) => {
return [dom.delete];
},
}}
/>
</ProForm.Item>
</div>
);
}
}}
</ProFormDependency>
</ProForm>
)}
</Card>
</PageContainer>
);
};
export default DutyInfo;
+23
View File
@@ -0,0 +1,23 @@
export type Shift = {
id: React.Key;
title: string;
place: string;
range: string[];
};
export type ServerInfo = {
isNormalDuty: boolean;
inDutyCnt: number;
currentDuty: string;
otherDutyTime: Shift[];
offDutyReason: string;
dutyRecoverTime: string;
}
export type Info = {
dutyStatus: string;
inDutyCnt: number;
otherDutyTime: Shift[];
offDutyReason: string;
dutyRecoverTime: string;
};
-14
View File
@@ -1,14 +0,0 @@
import { Todo } from '@/components';
import { PageContainer } from '@ant-design/pro-components';
import { Card } from 'antd';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<Todo />
</Card>
</PageContainer>
);
};
export default Logs;
@@ -0,0 +1,105 @@
import { Todo } from '@/components';
import { PageContainer } from '@ant-design/pro-components';
import { Card, Modal } from 'antd';
import React, { useState } from 'react';
const EditTable: React.FC = () => {
const [isSaveOpen, setIsSaveOpen] = useState(false);
const [isSetOpen, setIsSetOpen] = useState(false);
// const showSave = () => {
// setIsSaveOpen(true);
// };
const handleSaveOk = () => {
setIsSaveOpen(false);
};
const handleSaveCancel = () => {
setIsSaveOpen(false);
};
// const showSet = () => {
// setIsSetOpen(true);
// };
const handleSetOk = () => {
setIsSetOpen(false);
};
const handleSetCancel = () => {
setIsSetOpen(false);
};
// const columns: ProColumns<DataSourceType>[] = [
// {
// title: '活动名称',
// dataIndex: 'title',
// width: '30%',
// formItemProps: {
// rules: [
// {
// required: true,
// whitespace: true,
// message: '此项是必填项',
// },
// {
// message: '必须包含数字',
// pattern: /[0-9]/,
// },
// {
// max: 16,
// whitespace: true,
// message: '最长为 16 位',
// },
// {
// min: 6,
// whitespace: true,
// message: '最小为 6 位',
// },
// ],
// },
// },
// {
// title: '状态',
// key: 'state',
// dataIndex: 'state',
// valueType: 'select',
// valueEnum: {
// all: { text: '全部', status: 'Default' },
// open: {
// text: '未解决',
// status: 'Error',
// },
// closed: {
// text: '已解决',
// status: 'Success',
// },
// },
// },
// {
// title: '描述',
// dataIndex: 'decs',
// },
// ];
return (
<PageContainer>
<Modal title="确认保存?" open={isSaveOpen} onOk={handleSaveOk} onCancel={handleSaveCancel}>
<p>保存后将回到值班表页面!</p>
</Modal>
<Modal
title="确认设为当前值班表?"
open={isSetOpen}
onOk={handleSetOk}
onCancel={handleSetCancel}
>
<p>同一时间只能启用一份值班表!</p>
</Modal>
<Card>
<Todo />
</Card>
</PageContainer>
);
};
export default EditTable;
@@ -0,0 +1,10 @@
export type TableItem = {
id: React.Key;
mon?: string;
tue?: string;
wed?: string;
thu?: string;
fri?: string;
sat?: string;
sun?: string;
};
@@ -0,0 +1,6 @@
export type DutyTableItem = {
name: string;
isSelect: boolean;
time: string;
id: number;
};
+133
View File
@@ -0,0 +1,133 @@
import { PageContainer, ProList } from '@ant-design/pro-components';
import { history } from '@umijs/max';
import { Button, Card, Modal, Space, Tag } from 'antd';
import moment from 'moment';
import React, { useState } from 'react';
import request from 'umi-request';
import { DutyTableItem } from './dutyTableItem';
const DutyTable: React.FC = () => {
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
const [isSetOpen, setIsSetOpen] = useState(false);
const showDelete = () => {
setIsDeleteOpen(true);
};
const handleDeleteOk = () => {
setIsDeleteOpen(false);
};
const handleDeleteCancel = () => {
setIsDeleteOpen(false);
};
const showSet = () => {
setIsSetOpen(true);
};
const handleSetOk = () => {
setIsSetOpen(false);
};
const handleSetCancel = () => {
setIsSetOpen(false);
};
const newDutyTable = () => {
history.push('/admin/dutytable/edit');
};
return (
<PageContainer>
<Modal
title="确认删除?"
open={isDeleteOpen}
onOk={handleDeleteOk}
onCancel={handleDeleteCancel}
>
<p>删除后将无法恢复!</p>
</Modal>
<Modal
title="确认设为当前值班表?"
open={isSetOpen}
onOk={handleSetOk}
onCancel={handleSetCancel}
>
<p>同一时间只能启用一份值班表!</p>
</Modal>
<Card>
<ProList<DutyTableItem>
search={{
filterType: 'light',
}}
rowKey="op"
headerTitle="值班表列表"
request={async (params = {} as Record<string, any>) =>
request<{
data: DutyTableItem[];
}>('/admin/sheet/list', {
params,
})
}
pagination={{
pageSize: 10,
}}
metas={{
title: {
dataIndex: 'name',
title: '值班表',
},
subTitle: {
dataIndex: ['isSelect'],
render: (_, row) => {
return row.isSelect ? (
<Space size={0}>
<Tag color="blue">当前</Tag>
</Space>
) : (
<div></div>
);
},
title: '值班时间',
search: false,
},
extra: {
dataIndex: 'time',
render: (_: React.ReactNode, row: DutyTableItem) => {
return (
<div style={{ marginLeft: '3vh' }}>
{moment(row.time).format('YYYY-M-D HH:mm:ss')}
</div>
);
},
search: false,
},
actions: {
render: () => [
<a href="" target="_blank" rel="noopener noreferrer" key="view">
查看
</a>,
<a onClick={showSet} key="view">
设为当前值班表
</a>,
<a onClick={showDelete} key="view">
删除
</a>,
],
search: false,
},
}}
toolbar={{
actions: [
<Button type="primary" key="primary" onClick={newDutyTable}>
新建值班表
</Button>,
],
}}
/>
</Card>
</PageContainer>
);
};
export default DutyTable;
+115
View File
@@ -0,0 +1,115 @@
import { PageContainer, ProList } from '@ant-design/pro-components';
import { Card } from 'antd';
import moment from 'moment';
import request from 'umi-request';
import transInfo, { LogsItem } from './logItem';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<ProList<LogsItem>
search={{
filterType: 'light',
}}
rowKey="op"
headerTitle="日志列表"
request={async (params = {} as Record<string, any>) =>
request<{
data: LogsItem[];
}>('/admin/loglist', {
params,
})
}
pagination={{
pageSize: 10,
}}
metas={{
title: {
dataIndex: 'op',
title: '用户',
},
avatar: {
dataIndex: 'avatar',
search: false,
},
description: {
dataIndex: ['operation', 'target', 'prev', 'curr'],
search: false,
render: (_, row) => {
return (
<div>
{transInfo(row.operation, 'operation') +
'了' +
transInfo(row.target, 'target') +
':' +
row.prev.info +
' -> ' +
row.curr.info}
</div>
);
},
},
extra: {
dataIndex: 'time',
render: (_: React.ReactNode, row: LogsItem) => {
return (
<div style={{ marginLeft: '3vh' }}>
{moment(row.time).format('YYYY-M-D HH:mm:ss')}
</div>
);
},
search: false,
},
actions: {
render: () => [
<a href="" target="_blank" rel="noopener noreferrer" key="view">
查看
</a>,
],
search: false,
},
operation: {
// 自己扩展的字段,主要用于筛选,不在列表中显示
title: '操作',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
add: {
text: '增加',
status: '0',
},
delete: {
text: '删除',
status: '1',
},
change: {
text: '更改',
status: '2',
},
},
},
target: {
// 自己扩展的字段,主要用于筛选,不在列表中显示
title: '操作对象',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
dutyTable: {
text: '值班表',
},
dutyInfo: {
text: '值班信息',
},
conclusion: {
text: '值班总结',
},
},
},
}}
/>
</Card>
</PageContainer>
);
};
export default Logs;
+37
View File
@@ -0,0 +1,37 @@
const operationMap: { [key: number]: string } = {
0: '增加',
1: '删除',
2: '更改',
};
const targetMap: { [key: number]: string } = {
0: '值班表',
1: '值班信息',
2: '值班总结',
};
export type LogsItem = {
op: string;
time: string;
operation: number;
target: number;
prev: {
id: number;
info: string;
};
curr: {
id: number;
info: string;
};
};
const transInfo = (num: number, type: string) => {
switch (type) {
case 'operation':
return operationMap[num];
case 'target':
return targetMap[num];
}
};
export default transInfo;
@@ -0,0 +1,8 @@
export type AskLeaveItem = {
member: string;
time: string;
week: string;
shift: string;
reason: string;
substitute: string;
};
+124
View File
@@ -0,0 +1,124 @@
import { PageContainer, ProList } from '@ant-design/pro-components';
import { Card, Space, Tag } from 'antd';
import moment from 'moment';
import request from 'umi-request';
import { AskLeaveItem } from './askLeaveItem';
import transShift from '@/utils/common';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<ProList<AskLeaveItem>
search={{
filterType: 'light',
}}
rowKey="op"
headerTitle="请假列表"
request={async (params = {} as Record<string, any>) =>
request<{
data: AskLeaveItem[];
}>('/leader/askleavelist', {
params,
})
}
pagination={{
pageSize: 10,
}}
metas={{
title: {
dataIndex: 'member',
title: '用户',
},
avatar: {
dataIndex: 'avatar',
search: false,
},
subTitle: {
dataIndex: ['week', 'shift', 'substitute'],
render: (_, row) => {
return (
<Space size={0}>
<Tag color="blue" key={row.member}>
{transShift(row.week, row.shift)}
</Tag>
<Tag color={row.substitute ? 'green' : 'red'} key={row.member}>
{row.substitute ? '代班:' + row.substitute : '未找人代班'}
</Tag>
</Space>
);
},
title: '值班时间',
search: false,
},
description: {
dataIndex: 'reason',
search: false,
render: (_, row) => {
return <div>{row.reason}</div>;
},
},
extra: {
dataIndex: 'time',
render: (_: React.ReactNode, row: AskLeaveItem) => {
return (
<div style={{ marginLeft: '3vh' }}>
{moment(row.time).format('YYYY-M-D HH:mm:ss')}
</div>
);
},
search: false,
},
week: {
// 自己扩展的字段,主要用于筛选,不在列表中显示
title: '星期',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
mon: {
text: '周一',
},
tue: {
text: '周二',
},
wed: {
text: '周三',
},
thu: {
text: '周四',
},
fri: {
text: '周五',
},
sat: {
text: '周六',
},
sun: {
text: '周日',
},
},
},
shift: {
// 自己扩展的字段,主要用于筛选,不在列表中显示
title: '值班',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
first: {
text: '第一班',
},
second: {
text: '第二班',
},
third: {
text: '第三班',
},
},
},
}}
/>
</Card>
</PageContainer>
);
};
export default Logs;
-14
View File
@@ -1,14 +0,0 @@
import { Todo } from '@/components';
import { PageContainer } from '@ant-design/pro-components';
import { Card } from 'antd';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<Todo />
</Card>
</PageContainer>
);
};
export default Logs;
@@ -0,0 +1,7 @@
export type ConclusionItem = {
op: string;
time: string;
week: string;
shift: string;
detail: string;
};
+133
View File
@@ -0,0 +1,133 @@
import { PageContainer, ProList } from '@ant-design/pro-components';
import { Card, Space, Tag } from 'antd';
import moment from 'moment';
import request from 'umi-request';
import { ConclusionItem } from './conclusionItem';
import transShift from '@/utils/common';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<ProList<ConclusionItem>
search={{
filterType: 'light',
}}
rowKey="op"
headerTitle="值班总结列表"
request={async (params = {} as Record<string, any>) =>
request<{
data: ConclusionItem[];
}>('/leader/conclusionlist', {
params,
})
}
pagination={{
pageSize: 10,
}}
metas={{
title: {
dataIndex: 'op',
title: '用户',
},
avatar: {
dataIndex: 'avatar',
search: false,
},
subTitle: {
dataIndex: ['week', 'shift'],
render: (_, row) => {
return (
<Space size={0}>
<Tag color="blue" key={row.op}>
{transShift(row.week, row.shift)}
</Tag>
</Space>
);
},
title: '值班时间',
search: false,
},
description: {
dataIndex: 'detail',
search: false,
render: (_, row) => {
return (
<div>
{row.detail}
</div>
);
},
},
extra: {
dataIndex: 'time',
render: (_: React.ReactNode, row: ConclusionItem) => {
return (
<div style={{ marginLeft: '3vh' }}>
{moment(row.time).format('YYYY-M-D HH:mm:ss')}
</div>
);
},
search: false,
},
actions: {
render: () => [
<a href="" target="_blank" rel="noopener noreferrer" key="view">
查看
</a>,
],
search: false,
},
week: {
// 自己扩展的字段,主要用于筛选,不在列表中显示
title: '星期',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
mon: {
text: '周一',
},
tue: {
text: '周二',
},
wed: {
text: '周三',
},
thu: {
text: '周四',
},
fri: {
text: '周五',
},
sat: {
text: '周六',
},
sun: {
text: '周日',
},
},
},
shift: {
// 自己扩展的字段,主要用于筛选,不在列表中显示
title: '值班',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
first: {
text: '第一班',
},
second: {
text: '第二班',
},
third: {
text: '第三班',
},
},
},
}}
/>
</Card>
</PageContainer>
);
};
export default Logs;
+70
View File
@@ -0,0 +1,70 @@
import { PageContainer, ProList } from '@ant-design/pro-components';
import { Card, Space, Tag } from 'antd';
import moment from 'moment';
import request from 'umi-request';
import { ReportItem } from './reportItem';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<ProList<ReportItem>
search={{
filterType: 'light',
}}
rowKey="name"
headerTitle="反馈列表"
request={async (params = {} as Record<string, any>) =>
request<{
data: ReportItem[];
}>('/leader/reportlist', {
params,
})
}
pagination={{
pageSize: 10,
}}
metas={{
title: {
dataIndex: 'name',
title: '用户',
},
avatar: {
dataIndex: 'avatar',
search: false,
},
subTitle: {
dataIndex: 'phone',
render: (_, row) => {
return (
<Space size={0}>
<Tag color="blue" key={row.name}>
{row.phone}
</Tag>
</Space>
);
},
title: '联系方式',
},
description: {
dataIndex: 'text',
search: false,
},
extra: {
dataIndex: 'time',
render: (_: React.ReactNode, row: ReportItem) => {
return (
<div style={{ marginLeft: '3vh' }}>
{moment(row.time).format('YYYY-M-D HH:mm:ss')}
</div>
);
},
search: false,
},
}}
/>
</Card>
</PageContainer>
);
};
export default Logs;
+6
View File
@@ -0,0 +1,6 @@
export type ReportItem = {
name: string;
phone: string;
time: string;
text: string;
};
-14
View File
@@ -1,14 +0,0 @@
import { Todo } from '@/components';
import { PageContainer } from '@ant-design/pro-components';
import { Card } from 'antd';
const Logs: React.FC = () => {
return (
<PageContainer>
<Card>
<Todo />
</Card>
</PageContainer>
);
};
export default Logs;
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,7 +1,8 @@
import Settings from '@/../config/defaultSettings'; import Settings from '@/../config/defaultSettings';
import oidcLogo from '@/assets/oidc-logo.svg'; import oidcLogo from '@/assets/oidc-logo.svg';
import { PageFooter } from '@/components'; import { PageFooter } from '@/components';
import { login } from '@/services/ant-design-pro/api'; import { login } from '@/services/api';
import { API } from '@/services/typings';
import { LockOutlined, UserOutlined } from '@ant-design/icons'; import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { LoginForm, ProFormCheckbox, ProFormText } from '@ant-design/pro-components'; import { LoginForm, ProFormCheckbox, ProFormText } from '@ant-design/pro-components';
import { Helmet, history, useModel } from '@umijs/max'; import { Helmet, history, useModel } from '@umijs/max';
@@ -207,7 +208,6 @@ const Login: React.FC = () => {
</ProFormCheckbox> </ProFormCheckbox>
</div> </div>
<div style={{ marginBottom: '2vh' }}> <div style={{ marginBottom: '2vh' }}>
{' '}
<Button <Button
type="default" type="default"
block block
-96
View File
@@ -1,96 +0,0 @@
import { TestBrowser } from '@@/testBrowser';
import { act, fireEvent, render } from '@testing-library/react';
import React from 'react';
// @ts-ignore
import { startMock } from '@@/requestRecordMock';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
let server: {
close: () => void;
};
describe('Login Page', () => {
beforeAll(async () => {
server = await startMock({
port: 8000,
scene: 'login',
});
});
afterAll(() => {
server?.close();
});
it('should show login form', async () => {
const historyRef = React.createRef<any>();
const rootContainer = render(
<TestBrowser
historyRef={historyRef}
location={{
pathname: '/user/login',
}}
/>,
);
await rootContainer.findAllByText('Ant Design');
act(() => {
historyRef.current?.push('/user/login');
});
expect(rootContainer.baseElement?.querySelector('.ant-pro-form-login-desc')?.textContent).toBe(
'Ant Design is the most influential web design specification in Xihu district',
);
expect(rootContainer.asFragment()).toMatchSnapshot();
rootContainer.unmount();
});
it('should login success', async () => {
const historyRef = React.createRef<any>();
const rootContainer = render(
<TestBrowser
historyRef={historyRef}
location={{
pathname: '/user/login',
}}
/>,
);
await rootContainer.findAllByText('Ant Design');
const userNameInput = await rootContainer.findByPlaceholderText('Username: admin or user');
act(() => {
fireEvent.change(userNameInput, { target: { value: 'admin' } });
});
const passwordInput = await rootContainer.findByPlaceholderText('Password: ant.design');
act(() => {
fireEvent.change(passwordInput, { target: { value: 'ant.design' } });
});
await (await rootContainer.findByText('Login')).click();
// 等待接口返回结果
await waitTime(5000);
await rootContainer.findAllByText('Ant Design Pro');
expect(rootContainer.asFragment()).toMatchSnapshot();
await waitTime(2000);
rootContainer.unmount();
});
});
+122 -5
View File
@@ -1,13 +1,130 @@
import { Todo } from '@/components'; import { getStat } from '@/services/api';
import { PageContainer } from '@ant-design/pro-components'; import type { TinyRingConfig } from '@ant-design/charts';
import { Card } from 'antd'; import { Tiny } from '@ant-design/charts';
import { PageContainer, ProCard, StatisticCard } from '@ant-design/pro-components';
import { Card, Statistic, message } from 'antd';
import RcResizeObserver from 'rc-resize-observer';
import { useLayoutEffect, useState } from 'react';
import { Stat } from './stat';
const percentRing = (percent: number, frontColor: string): React.JSX.Element => {
const TinyRing = Tiny.Ring as (props: TinyRingConfig) => JSX.Element;
const config: TinyRingConfig = {
percent: percent / 100,
width: 120,
height: 120,
color: ['#E8EFF5', frontColor],
annotations: [
{
type: 'text',
style: {
text: `${percent}%`,
x: '50%',
y: '50%',
textAlign: 'center',
fontSize: 16,
fontStyle: 'bold',
},
},
],
};
return <TinyRing {...config} />;
};
const Logs: React.FC = () => { const Logs: React.FC = () => {
const [responsive, setResponsive] = useState(false);
const [messageApi, contextHolder] = message.useMessage();
const [stat, setStat] = useState<Stat>({
todayPT: 0,
totalPT: 0,
oreo: 0,
report: 0,
allSuccess: 0,
all: 0,
});
const [loading, setLoading] = useState(true);
useLayoutEffect(() => {
getStat()
.then((res) => {
setStat(res.data);
setLoading(false);
})
.catch((err) => {
messageApi.open({
type: 'error',
content: '获取失败!' + err,
});
});
}, []);
return ( return (
<PageContainer> <PageContainer>
<Card> {contextHolder}
<Todo /> {!loading && (
<Card style={{ color: '#f5f5f5' }}>
<RcResizeObserver
key="resize-observer"
onResize={(offset) => {
setResponsive(offset.width < 596);
}}
>
<ProCard split={responsive ? 'horizontal' : 'vertical'}>
<ProCard colSpan="Auto">
<ProCard hoverable>左侧内容</ProCard>
</ProCard>
<ProCard colSpan="170px">
<StatisticCard
hoverable
style={{ marginTop: '0vh' }}
statistic={{
title: '今日预填写工单数',
value: stat.todayPT,
description: (
<Statistic
style={{ marginTop: '15vh', marginBottom: '8vh' }}
title="今日反馈数"
value={stat.report}
/>
),
}}
/>
</ProCard>
<ProCard colSpan="360px">
<StatisticCard.Group direction={responsive ? 'row' : 'column'}>
<StatisticCard
hoverable
chart={percentRing(
Math.round(((stat.oreo / stat.totalPT) * 1000) / 10),
'#00CC66',
)}
statistic={{
title: '加入 Oreo 工单数',
value: stat.oreo,
description: <Statistic title="预填写工单总数" value={stat.totalPT} />,
}}
chartPlacement="left"
/>
<StatisticCard
hoverable
style={{ marginTop: '3vh' }}
chart={percentRing(
Math.round((stat.allSuccess / stat.all) * 1000) / 10,
'#66AFF4',
)}
statistic={{
title: '维修成功次数',
value: stat.allSuccess,
description: <Statistic title="Oreo、ETA 总工单数" value={stat.all} />,
}}
chartPlacement="left"
/>
</StatisticCard.Group>
</ProCard>
</ProCard>
</RcResizeObserver>
</Card> </Card>
)}
</PageContainer> </PageContainer>
); );
}; };
+8
View File
@@ -0,0 +1,8 @@
export type Stat = {
todayPT: number;
totalPT: number;
oreo: number;
report: number;
allSuccess: number;
all: number;
};
-94
View File
@@ -1,94 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** 获取当前的用户 GET /api/currentUser */
export async function currentUser(options?: { [key: string]: any }) {
return request<{
data: API.CurrentUser;
}>('/api/currentUser', {
method: 'GET',
...(options || {}),
});
}
/** 退出登录接口 POST /api/login/outLogin */
export async function outLogin(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/login/outLogin', {
method: 'POST',
...(options || {}),
});
}
/** 登录接口 POST /api/login/account */
export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
return request<API.LoginResult>('/api/login/account', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 此处后端没有提供注释 GET /api/notices */
export async function getNotices(options?: { [key: string]: any }) {
return request<API.NoticeIconList>('/api/notices', {
method: 'GET',
...(options || {}),
});
}
/** 获取规则列表 GET /api/rule */
export async function rule(
params: {
// query
/** 当前的页码 */
current?: number;
/** 页面的容量 */
pageSize?: number;
},
options?: { [key: string]: any },
) {
return request<API.RuleList>('/api/rule', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 更新规则 PUT /api/rule */
export async function updateRule(options?: { [key: string]: any }) {
return request<API.RuleListItem>('/api/rule', {
method: 'POST',
data: {
method: 'update',
...(options || {}),
},
});
}
/** 新建规则 POST /api/rule */
export async function addRule(options?: { [key: string]: any }) {
return request<API.RuleListItem>('/api/rule', {
method: 'POST',
data: {
method: 'post',
...(options || {}),
},
});
}
/** 删除规则 DELETE /api/rule */
export async function removeRule(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/rule', {
method: 'POST',
data: {
method: 'delete',
...(options || {}),
},
});
}
-21
View File
@@ -1,21 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** 发送验证码 POST /api/login/captcha */
export async function getFakeCaptcha(
params: {
// query
/** 手机号 */
phone?: string;
},
options?: { [key: string]: any },
) {
return request<API.FakeCaptcha>('/api/login/captcha', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
-101
View File
@@ -1,101 +0,0 @@
// @ts-ignore
/* eslint-disable */
declare namespace API {
type CurrentUser = {
name?: string;
avatar?: string;
userid?: string;
email?: string;
signature?: string;
title?: string;
group?: string;
tags?: { key?: string; label?: string }[];
notifyCount?: number;
unreadCount?: number;
country?: string;
access?: string;
geographic?: {
province?: { label?: string; key?: string };
city?: { label?: string; key?: string };
};
address?: string;
phone?: string;
};
type LoginResult = {
status?: string;
type?: string;
currentAuthority?: string;
};
type PageParams = {
current?: number;
pageSize?: number;
};
type RuleListItem = {
key?: number;
disabled?: boolean;
href?: string;
avatar?: string;
name?: string;
owner?: string;
desc?: string;
callNo?: number;
status?: number;
updatedAt?: string;
createdAt?: string;
progress?: number;
};
type RuleList = {
data?: RuleListItem[];
/** 列表的内容总数 */
total?: number;
success?: boolean;
};
type FakeCaptcha = {
code?: number;
status?: string;
};
type LoginParams = {
username?: string;
password?: string;
autoLogin?: boolean;
type?: string;
};
type ErrorResponse = {
/** 业务约定的错误码 */
errorCode: string;
/** 业务上的错误信息 */
errorMessage?: string;
/** 业务上的请求是否成功 */
success?: boolean;
};
type NoticeIconList = {
data?: NoticeIconItem[];
/** 列表的内容总数 */
total?: number;
success?: boolean;
};
type NoticeIconItemType = 'notification' | 'message' | 'event';
type NoticeIconItem = {
id?: string;
extra?: string;
key?: string;
read?: boolean;
avatar?: string;
title?: string;
status?: string;
datetime?: string;
description?: string;
type?: NoticeIconItemType;
};
}
+56
View File
@@ -0,0 +1,56 @@
// @ts-ignore
/* eslint-disable */
import { ServerInfo } from '@/pages/Admin/DutyInfo/info';
import { Stat } from '@/pages/Welcome/stat';
import { request } from '@umijs/max';
import { API } from './typings';
/** 获取当前的用户 GET /api/currentUser */
export async function currentUser(options?: { [key: string]: any }) {
return request<{
data: API.CurrentUser;
}>('/api/currentUser', {
method: 'GET',
...(options || {}),
});
}
/** 退出登录接口 POST /api/login/outLogin */
export async function outLogin(options?: { [key: string]: any }) {
return request<Record<string, any>>('/api/login/outLogin', {
method: 'POST',
...(options || {}),
});
}
/** 登录接口 POST /api/login/account */
export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
return request<API.LoginResult>('/api/login/account', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 获取首页信息 GET /stats */
export async function getStat(options?: { [key: string]: any }) {
return request<{
data: Stat;
}>('/stats', {
method: 'GET',
...(options || {}),
});
}
/** 获取当前值班信息 GET /admin/duty/current */
export async function getDutyInfo(options?: { [key: string]: any }) {
return request<{
data: ServerInfo;
}>('/admin/duty/current', {
method: 'GET',
...(options || {}),
});
}
@@ -3,8 +3,6 @@
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:
import * as api from './api'; import * as api from './api';
import * as login from './login';
export default { export default {
api, api,
login,
}; };
-12
View File
@@ -1,12 +0,0 @@
// @ts-ignore
/* eslint-disable */
// API 更新时间:
// API 唯一标识:
import * as pet from './pet';
import * as store from './store';
import * as user from './user';
export default {
pet,
store,
user,
};
-153
View File
@@ -1,153 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** Update an existing pet PUT /pet */
export async function updatePet(body: API.Pet, options?: { [key: string]: any }) {
return request<any>('/pet', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** Add a new pet to the store POST /pet */
export async function addPet(body: API.Pet, options?: { [key: string]: any }) {
return request<any>('/pet', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** Find pet by ID Returns a single pet GET /pet/${param0} */
export async function getPetById(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getPetByIdParams,
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
return request<API.Pet>(`/pet/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}
/** Updates a pet in the store with form data POST /pet/${param0} */
export async function updatePetWithForm(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.updatePetWithFormParams,
body: { name?: string; status?: string },
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
const formData = new FormData();
Object.keys(body).forEach((ele) => {
const item = (body as any)[ele];
if (item !== undefined && item !== null) {
formData.append(
ele,
typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
);
}
});
return request<any>(`/pet/${param0}`, {
method: 'POST',
params: { ...queryParams },
data: formData,
...(options || {}),
});
}
/** Deletes a pet DELETE /pet/${param0} */
export async function deletePet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deletePetParams & {
// header
api_key?: string;
},
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
return request<any>(`/pet/${param0}`, {
method: 'DELETE',
headers: {},
params: { ...queryParams },
...(options || {}),
});
}
/** uploads an image POST /pet/${param0}/uploadImage */
export async function uploadFile(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.uploadFileParams,
body: { additionalMetadata?: string; file?: string },
file?: File,
options?: { [key: string]: any },
) {
const { petId: param0, ...queryParams } = params;
const formData = new FormData();
if (file) {
formData.append('file', file);
}
Object.keys(body).forEach((ele) => {
const item = (body as any)[ele];
if (item !== undefined && item !== null) {
formData.append(
ele,
typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
);
}
});
return request<API.ApiResponse>(`/pet/${param0}/uploadImage`, {
method: 'POST',
params: { ...queryParams },
data: formData,
requestType: 'form',
...(options || {}),
});
}
/** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
export async function findPetsByStatus(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.findPetsByStatusParams,
options?: { [key: string]: any },
) {
return request<API.Pet[]>('/pet/findByStatus', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
export async function findPetsByTags(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.findPetsByTagsParams,
options?: { [key: string]: any },
) {
return request<API.Pet[]>('/pet/findByTags', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
-48
View File
@@ -1,48 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
export async function getInventory(options?: { [key: string]: any }) {
return request<Record<string, any>>('/store/inventory', {
method: 'GET',
...(options || {}),
});
}
/** Place an order for a pet POST /store/order */
export async function placeOrder(body: API.Order, options?: { [key: string]: any }) {
return request<API.Order>('/store/order', {
method: 'POST',
data: body,
...(options || {}),
});
}
/** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
export async function getOrderById(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getOrderByIdParams,
options?: { [key: string]: any },
) {
const { orderId: param0, ...queryParams } = params;
return request<API.Order>(`/store/order/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}
/** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
export async function deleteOrder(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteOrderParams,
options?: { [key: string]: any },
) {
const { orderId: param0, ...queryParams } = params;
return request<any>(`/store/order/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
-112
View File
@@ -1,112 +0,0 @@
declare namespace API {
type ApiResponse = {
code?: number;
type?: string;
message?: string;
};
type Category = {
id?: number;
name?: string;
};
type deleteOrderParams = {
/** ID of the order that needs to be deleted */
orderId: number;
};
type deletePetParams = {
api_key?: string;
/** Pet id to delete */
petId: number;
};
type deleteUserParams = {
/** The name that needs to be deleted */
username: string;
};
type findPetsByStatusParams = {
/** Status values that need to be considered for filter */
status: ('available' | 'pending' | 'sold')[];
};
type findPetsByTagsParams = {
/** Tags to filter by */
tags: string[];
};
type getOrderByIdParams = {
/** ID of pet that needs to be fetched */
orderId: number;
};
type getPetByIdParams = {
/** ID of pet to return */
petId: number;
};
type getUserByNameParams = {
/** The name that needs to be fetched. Use user1 for testing. */
username: string;
};
type loginUserParams = {
/** The user name for login */
username: string;
/** The password for login in clear text */
password: string;
};
type Order = {
id?: number;
petId?: number;
quantity?: number;
shipDate?: string;
/** Order Status */
status?: 'placed' | 'approved' | 'delivered';
complete?: boolean;
};
type Pet = {
id?: number;
category?: Category;
name: string;
photoUrls: string[];
tags?: Tag[];
/** pet status in the store */
status?: 'available' | 'pending' | 'sold';
};
type Tag = {
id?: number;
name?: string;
};
type updatePetWithFormParams = {
/** ID of pet that needs to be updated */
petId: number;
};
type updateUserParams = {
/** name that need to be updated */
username: string;
};
type uploadFileParams = {
/** ID of pet to update */
petId: number;
};
type User = {
id?: number;
username?: string;
firstName?: string;
lastName?: string;
email?: string;
password?: string;
phone?: string;
/** User Status */
userStatus?: number;
};
}
-100
View File
@@ -1,100 +0,0 @@
// @ts-ignore
/* eslint-disable */
import { request } from '@umijs/max';
/** Create user This can only be done by the logged in user. POST /user */
export async function createUser(body: API.User, options?: { [key: string]: any }) {
return request<any>('/user', {
method: 'POST',
data: body,
...(options || {}),
});
}
/** Get user by user name GET /user/${param0} */
export async function getUserByName(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getUserByNameParams,
options?: { [key: string]: any },
) {
const { username: param0, ...queryParams } = params;
return request<API.User>(`/user/${param0}`, {
method: 'GET',
params: { ...queryParams },
...(options || {}),
});
}
/** Updated user This can only be done by the logged in user. PUT /user/${param0} */
export async function updateUser(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.updateUserParams,
body: API.User,
options?: { [key: string]: any },
) {
const { username: param0, ...queryParams } = params;
return request<any>(`/user/${param0}`, {
method: 'PUT',
params: { ...queryParams },
data: body,
...(options || {}),
});
}
/** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
export async function deleteUser(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteUserParams,
options?: { [key: string]: any },
) {
const { username: param0, ...queryParams } = params;
return request<any>(`/user/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
/** Creates list of users with given input array POST /user/createWithArray */
export async function createUsersWithArrayInput(
body: API.User[],
options?: { [key: string]: any },
) {
return request<any>('/user/createWithArray', {
method: 'POST',
data: body,
...(options || {}),
});
}
/** Creates list of users with given input array POST /user/createWithList */
export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) {
return request<any>('/user/createWithList', {
method: 'POST',
data: body,
...(options || {}),
});
}
/** Logs user into the system GET /user/login */
export async function loginUser(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.loginUserParams,
options?: { [key: string]: any },
) {
return request<string>('/user/login', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** Logs out current logged in user session GET /user/logout */
export async function logoutUser(options?: { [key: string]: any }) {
return request<any>('/user/logout', {
method: 'GET',
...(options || {}),
});
}
+31
View File
@@ -0,0 +1,31 @@
import { LogsItem } from '@/pages/Admin/Logs/logItem';
declare namespace API {
type CurrentUser = {
name?: string;
avatar?: string;
userid?: string;
group?: string;
access?: string;
};
type LoginResult = {
status?: string;
type?: string;
currentAuthority?: string;
};
type LoginParams = {
username?: string;
password?: string;
autoLogin?: boolean;
type?: string;
};
type LogList = {
pageIndex: number;
pageCount: number;
size: number;
data: LogsItem[];
};
}
+25
View File
@@ -0,0 +1,25 @@
const weekMap: { [key: string]: string } = {
'1': '周一',
'2': '周二',
'3': '周三',
'4': '周四',
'5': '周五',
'6': '周六',
'7': '周日',
};
const shiftMap: { [key: string]: string } = {
'1': '第一班',
'2': '第二班',
'3': '第三班',
};
const transShift = (week: string, shift: string) => {
if (week === '7' && shift === '3') {
return '其他值班时间';
} else {
return weekMap[week] + shiftMap[shift];
}
};
export default transShift;