7 Commits
Author SHA1 Message Date
fracher 787b52c096 add Todo 2024-03-14 19:14:53 +08:00
fracher 3619ce3d1e update README.md 2024-03-14 18:58:49 +08:00
fracher c24a82d177 improve define menu 2024-03-14 02:33:39 +08:00
fracher ec711daa0c clear code 2024-03-14 02:23:36 +08:00
fracher d91ece0c54 improve footer 2024-03-14 02:20:23 +08:00
fracher 1937fa4217 fix breadcrumb 2024-03-14 02:09:11 +08:00
fracher 50e3f4a8f0 App layout 2024-03-14 01:44:38 +08:00
11 changed files with 1135 additions and 330 deletions
+11 -66
View File
@@ -1,75 +1,20 @@
# EVA Notify
# Notify Admin
## 简介
## 目标
使用微信小程序实现的值班信息查看、维修进度交流系统。
1. 微信小程序的信息管理页面
2. 人资部后台
## 功能
### 值班情况
### 主席团管理页面
1. 能看到现在维修状况:有几台正在维修,有几个人值班(空闲状态)
2. 能看到自己机器的维修情况:维修进度、评论等
1. 使用手机号确定id
1. 值班表导入、更新
2. 成员权限管理
3. 特殊维修时间(如学园维修)、值班信息更新
### 互动
### 值班组长管理页面
1. 提醒:当修好之后会向你发送信息,提醒来取
2. 线上答疑:协会成员登录账号,看到机主提出的问题可以回答(暂定由值班组长管理)
1. 公开答疑仅回复能不能修,更复杂的问题推荐其来204
2. 当自己的机器正在维修时,可以与维修人员交流进度和问题等
3. (暂定)答疑功能使用预填写工单、工单的评论实现
ps. 是微信小程序中小组管理页面的pc端替代
### 信息展示
1. 展示协会值班时间(现在是否在值班),若不在值班显示预计值班时间、不值班的原因等
2. 展示维修流程,包括注意事项等
1. 戴尔、Surface等不拆
2. 数据备份提醒
3. 不收礼物
4. 不要在204饮食
3. (备选)其他语言本地化
### 面向协会成员
1. 权限管理
2. 值班组长签到情况、值班总结等
3. 显示自己什么时候值班
4. 提醒事项
1. 值班后收拾东西、交接
2. 螺丝记得拧上
3. 某些东西不要放在维修篮里
### 对接
1. 与EVA统一身份认证对接
1. 协会成员登录入口
2. 个人信息同步
2. 与Oreo对接
1. 维修工单列表查看
2. 将预填写工单加入Oreo
3. 防恶意行为
1. 工单填写间隔限制为【1】分钟
2. 一个人一天最多能创建【10】个预填写工单
3. 黑名单机制
## UI
### 主页
1. 信息显示
2. (协会成员)相关信息
3. 提醒事项
4. 如何联系到协会
### 维修
1. (面向机主)预填写工单
2. 维修进度提示
3. (面向协会成员)维修列表
### 我的
1. 更改手机号等个人信息(手机号可以填写多个)
2. (备选)修改用户名、头像
3. 设置真名(可帮助预填写工单)
1. 值班组长上传本班相关信息
+921 -206
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -9,13 +9,12 @@
"@types/node": "^16.18.88",
"@types/react": "^18.2.65",
"@types/react-dom": "^18.2.21",
"evergreen-ui": "^7.1.9",
"antd": "^5.15.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
"typescript": "^4.9.5"
},
"scripts": {
"start": "react-scripts start",
@@ -39,7 +38,6 @@
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"sass": "^1.71.1",
"stylelint": "^16.2.1"
},
"lint-staged": {
+2 -21
View File
@@ -15,29 +15,10 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>EVA-Notify Admin Page</title>
</head>
<body>
<body style="margin: 0;">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
+148 -10
View File
@@ -1,13 +1,151 @@
import { Component, ReactNode } from 'react';
import { Route, Routes } from 'react-router-dom';
import { useState, FC } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import {
TeamOutlined,
UserOutlined,
DesktopOutlined,
HomeOutlined,
} from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { Breadcrumb, Layout, Menu, theme } from 'antd';
import { AppRouter } from './router';
import PageFooter from './components/PageFooter';
const { Content, Sider } = Layout;
type MenuItem = Required<MenuProps>['items'][number];
type KeyRouterMap = Map<React.Key, string>;
const keyRouterMap: KeyRouterMap = new Map();
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
): MenuItem {
return {
key,
icon,
children,
label,
} as MenuItem;
}
function defMenu(
label: React.ReactNode,
key: React.Key,
icon: React.ReactNode,
path: string,
): MenuItem {
keyRouterMap.set(key, path);
return getItem(label, key, icon);
}
function defRootMenu(
label: React.ReactNode,
key: React.Key,
icon: React.ReactNode,
children: MenuItem[],
): MenuItem {
return getItem(label, key, icon, children);
}
function defSubMenu(
label: React.ReactNode,
key: React.Key,
path: string,
): MenuItem {
keyRouterMap.set(key, path);
return getItem(label, key);
}
const items: MenuItem[] = [
defMenu('主页', 'mainpage', <DesktopOutlined />, '/'),
defRootMenu('展示信息管理', 'sub1', <UserOutlined />, [
defSubMenu('选项1', '1', '/admin/hello1'),
defSubMenu('选项2', '2', '/admin/hello2'),
defSubMenu('选项3', '3', '/admin/hello3'),
]),
defRootMenu('值班组长', 'sub2', <TeamOutlined />, [
defSubMenu('选项1', '4', '/duty/hi1'),
defSubMenu('选项2', '5', '/duty/hi2'),
]),
defMenu('关于我们', 'aboutpage', <UserOutlined />, '/about'),
];
interface BreadcrumbItem {
href: string;
title: JSX.Element;
}
const AppLayout: FC<{ router: JSX.Element }> = (props) => {
const [collapsed, setCollapsed] = useState(false);
const { colorBgContainer, borderRadiusLG } = theme.getDesignToken();
const navigate = useNavigate();
const location = useLocation();
const pathArray = location.pathname.split('/');
const currentRouteItems: BreadcrumbItem[] = [
{
href: '/',
title: <HomeOutlined />,
},
];
for (var i = 1; i < pathArray.length; i++) {
currentRouteItems.push({
href: '',
title: <span>{pathArray[i]}</span>,
});
}
export default class App extends Component {
render(): ReactNode {
return (
<Routes>
<Route path='/' element={<p>Main</p>} />
<Route path='/hello' element={<p>Hello</p>} />
</Routes>
<Layout style={{ minHeight: '100vh' }}>
<Sider
collapsible
collapsed={collapsed}
onCollapse={(value) => setCollapsed(value)}
>
<div className='demo-logo-vertical' />
<Menu
theme='dark'
defaultSelectedKeys={['1']}
mode='inline'
items={items}
onClick={(info: { key: React.Key }) => {
console.log('path', keyRouterMap.get(info.key));
navigate(keyRouterMap.get(info.key) || '/');
}}
/>
</Sider>
<Layout>
<Content style={{ margin: '0 16px' }}>
<Breadcrumb style={{ margin: '16px 0' }} items={currentRouteItems} />
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
<div>{props.router}</div>
</div>
</Content>
<PageFooter />
</Layout>
</Layout>
);
}
}
};
const App: FC = () => {
return (
<div className='app'>
<AppLayout router={<AppRouter />} />
</div>
);
};
export default App;
+21
View File
@@ -0,0 +1,21 @@
import { Layout } from 'antd';
const { Footer } = Layout;
const PageFooter: React.FC = () => {
const startYear = 2024;
const curYear = new Date().getFullYear();
let yearStr = '';
if (startYear === curYear) {
yearStr = curYear.toString();
} else {
yearStr = startYear.toString() + ' - ' + curYear.toString();
}
return (
<Footer style={{ textAlign: 'center' }}>
EVA-Notify Admin Page &copy; {yearStr} EVA Tech
</Footer>
);
};
export default PageFooter;
+14
View File
@@ -0,0 +1,14 @@
import { Typography } from 'antd';
const { Title, Text } = Typography;
const Todo: React.FC = () => {
return (
<div>
<Title level={1}>施工中...</Title>
<Text type={'secondary'}>{'// TODO'}</Text>
</div>
);
};
export default Todo;
-6
View File
@@ -1,7 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(
@@ -14,8 +13,3 @@ root.render(
</BrowserRouter>
</React.StrictMode>,
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

-15
View File
@@ -1,15 +0,0 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
+15
View File
@@ -0,0 +1,15 @@
import { Routes, Route } from 'react-router-dom';
import Todo from './components/Todo';
export const AppRouter: React.FC = () => {
return (
<Routes>
<Route path='/' element={<Todo />} />
<Route path='/admin/hello1' element={<Todo />} />
<Route path='/admin/hello2' element={<Todo />} />
<Route path='/admin/hello3' element={<Todo />} />
<Route path='/duty/hi1' element={<Todo />} />
<Route path='/duty/hi2' element={<Todo />} />
</Routes>
);
};