├── .DS_Store ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .stylelintrc.js ├── LICENSE ├── README.md ├── config ├── config.dev.ts ├── config.ts ├── defaultSettings.ts ├── oneapi.json ├── proxy.ts └── routes.ts ├── jest.config.js ├── jsconfig.json ├── mock ├── listTableList.ts ├── notices.ts ├── route.ts └── user.ts ├── package.json ├── public ├── .DS_Store ├── CNAME ├── favicon.ico ├── icons │ ├── icon-128x128.png │ ├── icon-192x192.png │ └── icon-512x512.png ├── logo.svg ├── logo1.svg └── pro_icon.svg ├── src ├── access.ts ├── app.tsx ├── components │ ├── Footer │ │ └── index.tsx │ ├── HeaderDropdown │ │ ├── index.less │ │ └── index.tsx │ ├── HeaderSearch │ │ ├── index.less │ │ └── index.tsx │ ├── NoticeIcon │ │ ├── NoticeIcon.tsx │ │ ├── NoticeList.less │ │ ├── NoticeList.tsx │ │ ├── index.less │ │ └── index.tsx │ ├── RightContent │ │ ├── AvatarDropdown.tsx │ │ ├── index.less │ │ └── index.tsx │ ├── ToolBar │ │ └── index.tsx │ └── index.md ├── e2e │ └── baseLayout.e2e.spec.ts ├── global.less ├── global.tsx ├── layouts │ └── BaseLayout.tsx ├── manifest.json ├── pages │ ├── 404.tsx │ ├── Admin.tsx │ ├── Table │ │ ├── index.less │ │ └── index.tsx │ ├── TableList │ │ ├── components │ │ │ └── UpdateForm.tsx │ │ └── index.tsx │ ├── Welcome.less │ ├── Welcome.tsx │ ├── admin │ │ ├── logs │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── menus │ │ │ ├── components │ │ │ │ ├── BaseFormItems.tsx │ │ │ │ ├── CreateForm.tsx │ │ │ │ └── UpdateForm.tsx │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── permissions │ │ │ ├── components │ │ │ │ ├── CreateForm.tsx │ │ │ │ ├── ShowForm.tsx │ │ │ │ └── UpdateForm.tsx │ │ │ └── index.tsx │ │ ├── roles │ │ │ ├── components │ │ │ │ ├── CreateForm.tsx │ │ │ │ ├── ShowForm.tsx │ │ │ │ └── UpdateForm.tsx │ │ │ ├── index.less │ │ │ └── index.tsx │ │ └── users │ │ │ ├── components │ │ │ ├── CreateForm.tsx │ │ │ ├── ShowForm.tsx │ │ │ └── UpdateForm.tsx │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── document.ejs │ └── user │ │ └── Login │ │ ├── index.less │ │ └── index.tsx ├── service-worker.js ├── services │ ├── API.d.ts │ ├── admin │ │ ├── logs.d.ts │ │ ├── logs.ts │ │ ├── menu.d.ts │ │ ├── menu.ts │ │ ├── permission.d.ts │ │ ├── permission.ts │ │ ├── role.d.ts │ │ ├── role.ts │ │ ├── user.d.ts │ │ └── user.ts │ ├── ant-design-pro │ │ ├── api.ts │ │ ├── index.ts │ │ ├── login.ts │ │ └── typings.d.ts │ └── swagger │ │ ├── index.ts │ │ ├── pet.ts │ │ ├── store.ts │ │ ├── typings.d.ts │ │ └── user.ts ├── typings.d.ts └── utils │ ├── utils.less │ └── utils.ts ├── tests ├── run-tests.js └── setupTests.js └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutUI/out-admin-react/3470d8b926d17d75529268a61edcf2bf96f4ce00/.DS_Store -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | .history 5 | /src/.umi/**/*.* -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve("@umijs/fabric/dist/eslint")], 3 | globals: { 4 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, 5 | page: true, 6 | REACT_APP_ENV: true, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/.umi 3 | dist 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require("@umijs/fabric"); 2 | 3 | module.exports = { 4 | ...fabric.stylelint, 5 | }; 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OutUI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## out-admin-react 2 | 3 |

out-admin-react是使用 react + antd pro v5 开发的一个后台权限管理系统系统 4 |

5 | 6 | codecov 7 | 8 | 9 | codecov 10 | 11 |

12 | 13 | ## 运行 14 | 15 | ### npm 16 | 17 | ```bash 18 | $ npm i 19 | $ npm run dev 20 | ``` 21 | 22 | ### yarn 23 | 24 | ```bash 25 | $ yarn 26 | $ yarn run dev 27 | ``` 28 | 29 | ## 登录账号 30 | 31 | ``` 32 | admin/123456 33 | ``` 34 | 35 | ## 项目演示: 36 | 37 | ![image](https://github.com/Outjs/static/blob/main/outjs/ui-3.png) 38 | 39 | ![image](https://github.com/Outjs/static/blob/main/outjs/ui-1.png) 40 | 41 | ![image](https://github.com/Outjs/static/blob/main/outjs/ui-2.png) 42 | 43 | ## 项目后端 44 | 45 | 配套的后端工程请移步 https://github.com/Outjs/out-admin-midway 46 | 启动后端可以体验完整功能 47 | 48 | ## 欢迎Star和PR 49 | 如果项目对你有帮助,点颗星支持一下,后续还会推出更多功能 50 | -------------------------------------------------------------------------------- /config/config.dev.ts: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from "umi"; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | // https://github.com/zthxxx/react-dev-inspector 7 | "react-dev-inspector/plugins/umi/react-inspector", 8 | ], 9 | // https://github.com/zthxxx/react-dev-inspector#inspector-loader-props 10 | inspectorConfig: { 11 | exclude: [], 12 | babelPlugins: [], 13 | babelOptions: {}, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /config/config.ts: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from "umi"; 3 | import { join } from "path"; 4 | import defaultSettings from "./defaultSettings"; 5 | import proxy from "./proxy"; 6 | import routes from "./routes"; 7 | const { REACT_APP_ENV } = process.env; 8 | export default defineConfig({ 9 | hash: true, 10 | antd: {}, 11 | dva: { 12 | hmr: true, 13 | }, 14 | layout: { 15 | // https://umijs.org/zh-CN/plugins/plugin-layout 16 | locale: true, 17 | siderWidth: 105, 18 | ...defaultSettings, 19 | }, 20 | dynamicImport: { 21 | loading: "@ant-design/pro-layout/es/PageLoading", 22 | }, 23 | targets: { 24 | ie: 11, 25 | }, 26 | // umi routes: https://umijs.org/docs/routing 27 | routes, 28 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn 29 | theme: { 30 | "root-entry-name": "variable", 31 | "font-size-base": "12px", 32 | }, 33 | // esbuild is father build tools 34 | // https://umijs.org/plugins/plugin-esbuild 35 | esbuild: {}, 36 | title: false, 37 | ignoreMomentLocale: true, 38 | proxy: proxy[REACT_APP_ENV || "dev"], 39 | manifest: { 40 | basePath: "/", 41 | }, 42 | // Fast Refresh 热更新 43 | fastRefresh: {}, 44 | openAPI: [ 45 | { 46 | requestLibPath: "import { request } from 'umi'", 47 | // 或者使用在线的版本 48 | // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json" 49 | schemaPath: join(__dirname, "oneapi.json"), 50 | mock: false, 51 | }, 52 | { 53 | requestLibPath: "import { request } from 'umi'", 54 | schemaPath: 55 | "https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json", 56 | projectName: "swagger", 57 | }, 58 | ], 59 | nodeModulesTransform: { 60 | type: "none", 61 | }, 62 | mfsu: {}, 63 | webpack5: {}, 64 | exportStatic: {}, 65 | }); 66 | -------------------------------------------------------------------------------- /config/defaultSettings.ts: -------------------------------------------------------------------------------- 1 | import { Settings as LayoutSettings } from "@ant-design/pro-layout"; 2 | 3 | const Settings: LayoutSettings & { 4 | pwa?: boolean; 5 | logo?: string; 6 | } = { 7 | title: false, 8 | navTheme: "dark", 9 | primaryColor: "#1890ff", 10 | layout: "side", 11 | contentWidth: "Fluid", 12 | fixedHeader: true, 13 | fixSiderbar: true, 14 | pwa: false, 15 | logo: "/logo.svg", 16 | headerHeight: 48, 17 | splitMenus: false, 18 | //headerRender: false, 19 | footerRender: false, 20 | }; 21 | 22 | export default Settings; 23 | -------------------------------------------------------------------------------- /config/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 3 | * ------------------------------- 4 | * The agent cannot take effect in the production environment 5 | * so there is no configuration of the production environment 6 | * For details, please see 7 | * https://pro.ant.design/docs/deploy 8 | */ 9 | export default { 10 | dev: { 11 | // localhost:8000/api/** -> https://preview.pro.ant.design/api/** 12 | "/api/": { 13 | // 要代理的地址 14 | target: "http://localhost:7001", 15 | // 配置了这个可以从 http 代理到 https 16 | // 依赖 origin 的功能可能需要这个,比如 cookie 17 | changeOrigin: true, 18 | pathRewrite: { "/api": "/api" }, 19 | }, 20 | }, 21 | test: { 22 | "/api/": { 23 | target: "https://proapi.azurewebsites.net", 24 | changeOrigin: true, 25 | pathRewrite: { "^": "" }, 26 | }, 27 | }, 28 | pre: { 29 | "/api/": { 30 | target: "your pre url", 31 | changeOrigin: true, 32 | pathRewrite: { "^": "" }, 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /config/routes.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | path: "/user", 4 | layout: false, 5 | routes: [ 6 | { 7 | path: "/user", 8 | routes: [ 9 | { name: "登录", path: "/user/login", component: "./user/Login" }, 10 | ], 11 | }, 12 | { component: "./404" }, 13 | ], 14 | }, 15 | { 16 | path: "/table", 17 | name: "桌台", 18 | icon: "icon-zhuozi", 19 | access: "canAdmin", 20 | component: "./Table", 21 | }, 22 | { 23 | path: "/welcome", 24 | name: "订餐", 25 | icon: "icon-diancan", 26 | component: "./Welcome", 27 | }, 28 | { 29 | name: "订单", 30 | icon: "icon-dingdan", 31 | path: "/list", 32 | component: "./TableList", 33 | }, 34 | { 35 | path: "/report", 36 | name: "统计", 37 | icon: "icon-shuju", 38 | access: "canAdmin", 39 | component: "./Admin", 40 | }, 41 | { 42 | path: "/admin", 43 | name: "管理", 44 | icon: "icon-xitongguanli", 45 | access: "canAdmin", 46 | //component: './admin', 47 | routes: [ 48 | { 49 | path: "/admin/user", 50 | name: "用户管理", 51 | //hideInMenu: true, 52 | icon: "smile", 53 | component: "./admin/users", 54 | }, 55 | { 56 | path: "/admin/role", 57 | name: "角色管理", 58 | ///hideInMenu: true, 59 | icon: "smile", 60 | component: "./admin/roles", 61 | }, 62 | { 63 | path: "/admin/permission", 64 | name: "权限管理", 65 | ///hideInMenu: true, 66 | icon: "smile", 67 | component: "./admin/permissions", 68 | }, 69 | { 70 | path: "/admin/menu", 71 | name: "菜单管理", 72 | ///hideInMenu: true, 73 | icon: "smile", 74 | component: "./admin/menus", 75 | }, 76 | { 77 | path: "/admin/log", 78 | name: "操作日志", 79 | ///hideInMenu: true, 80 | icon: "smile", 81 | component: "./admin/logs", 82 | }, 83 | { component: "./404" }, 84 | ], 85 | }, 86 | { path: "/", redirect: "/welcome" }, 87 | { component: "./404" }, 88 | ]; 89 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: "http://localhost:8000", 3 | verbose: false, 4 | extraSetupFiles: ["./tests/setupTests.js"], 5 | globals: { 6 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, 7 | localStorage: null, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/*": ["./src/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mock/listTableList.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import moment from "moment"; 3 | import { parse } from "url"; 4 | 5 | // mock tableListDataSource 6 | const genList = (current: number, pageSize: number) => { 7 | const tableListDataSource: API.RuleListItem[] = []; 8 | 9 | for (let i = 0; i < pageSize; i += 1) { 10 | const index = (current - 1) * 10 + i; 11 | tableListDataSource.push({ 12 | key: index, 13 | disabled: i % 6 === 0, 14 | href: "https://ant.design", 15 | avatar: [ 16 | "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", 17 | "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", 18 | ][i % 2], 19 | name: `TradeCode ${index}`, 20 | owner: "曲丽丽", 21 | desc: "这是一段描述", 22 | callNo: Math.floor(Math.random() * 1000), 23 | status: Math.floor(Math.random() * 10) % 4, 24 | updatedAt: moment().format("YYYY-MM-DD"), 25 | createdAt: moment().format("YYYY-MM-DD"), 26 | progress: Math.ceil(Math.random() * 100), 27 | }); 28 | } 29 | tableListDataSource.reverse(); 30 | return tableListDataSource; 31 | }; 32 | 33 | let tableListDataSource = genList(1, 100); 34 | 35 | function getRule(req: Request, res: Response, u: string) { 36 | let realUrl = u; 37 | if ( 38 | !realUrl || 39 | Object.prototype.toString.call(realUrl) !== "[object String]" 40 | ) { 41 | realUrl = req.url; 42 | } 43 | const { current = 1, pageSize = 10 } = req.query; 44 | const params = parse(realUrl, true).query as unknown as API.PageParams & 45 | API.RuleListItem & { 46 | sorter: any; 47 | filter: any; 48 | }; 49 | 50 | let dataSource = [...tableListDataSource].slice( 51 | ((current as number) - 1) * (pageSize as number), 52 | (current as number) * (pageSize as number) 53 | ); 54 | if (params.sorter) { 55 | const sorter = JSON.parse(params.sorter); 56 | dataSource = dataSource.sort((prev, next) => { 57 | let sortNumber = 0; 58 | Object.keys(sorter).forEach((key) => { 59 | if (sorter[key] === "descend") { 60 | if (prev[key] - next[key] > 0) { 61 | sortNumber += -1; 62 | } else { 63 | sortNumber += 1; 64 | } 65 | return; 66 | } 67 | if (prev[key] - next[key] > 0) { 68 | sortNumber += 1; 69 | } else { 70 | sortNumber += -1; 71 | } 72 | }); 73 | return sortNumber; 74 | }); 75 | } 76 | if (params.filter) { 77 | const filter = JSON.parse(params.filter as any) as { 78 | [key: string]: string[]; 79 | }; 80 | if (Object.keys(filter).length > 0) { 81 | dataSource = dataSource.filter((item) => { 82 | return Object.keys(filter).some((key) => { 83 | if (!filter[key]) { 84 | return true; 85 | } 86 | if (filter[key].includes(`${item[key]}`)) { 87 | return true; 88 | } 89 | return false; 90 | }); 91 | }); 92 | } 93 | } 94 | 95 | if (params.name) { 96 | dataSource = dataSource.filter((data) => 97 | data?.name?.includes(params.name || "") 98 | ); 99 | } 100 | const result = { 101 | data: dataSource, 102 | total: tableListDataSource.length, 103 | success: true, 104 | pageSize, 105 | current: parseInt(`${params.current}`, 10) || 1, 106 | }; 107 | 108 | return res.json(result); 109 | } 110 | 111 | function postRule(req: Request, res: Response, u: string, b: Request) { 112 | let realUrl = u; 113 | if ( 114 | !realUrl || 115 | Object.prototype.toString.call(realUrl) !== "[object String]" 116 | ) { 117 | realUrl = req.url; 118 | } 119 | 120 | const body = (b && b.body) || req.body; 121 | const { method, name, desc, key } = body; 122 | 123 | switch (method) { 124 | /* eslint no-case-declarations:0 */ 125 | case "delete": 126 | tableListDataSource = tableListDataSource.filter( 127 | (item) => key.indexOf(item.key) === -1 128 | ); 129 | break; 130 | case "post": 131 | (() => { 132 | const i = Math.ceil(Math.random() * 10000); 133 | const newRule: API.RuleListItem = { 134 | key: tableListDataSource.length, 135 | href: "https://ant.design", 136 | avatar: [ 137 | "https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png", 138 | "https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png", 139 | ][i % 2], 140 | name, 141 | owner: "曲丽丽", 142 | desc, 143 | callNo: Math.floor(Math.random() * 1000), 144 | status: Math.floor(Math.random() * 10) % 2, 145 | updatedAt: moment().format("YYYY-MM-DD"), 146 | createdAt: moment().format("YYYY-MM-DD"), 147 | progress: Math.ceil(Math.random() * 100), 148 | }; 149 | tableListDataSource.unshift(newRule); 150 | return res.json(newRule); 151 | })(); 152 | return; 153 | 154 | case "update": 155 | (() => { 156 | let newRule = {}; 157 | tableListDataSource = tableListDataSource.map((item) => { 158 | if (item.key === key) { 159 | newRule = { ...item, desc, name }; 160 | return { ...item, desc, name }; 161 | } 162 | return item; 163 | }); 164 | return res.json(newRule); 165 | })(); 166 | return; 167 | default: 168 | break; 169 | } 170 | 171 | const result = { 172 | list: tableListDataSource, 173 | pagination: { 174 | total: tableListDataSource.length, 175 | }, 176 | }; 177 | 178 | res.json(result); 179 | } 180 | 181 | export default { 182 | "GET /api/rule": getRule, 183 | "POST /api/rule": postRule, 184 | }; 185 | -------------------------------------------------------------------------------- /mock/notices.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | 3 | const getNotices = (req: Request, res: Response) => { 4 | res.json({ 5 | data: [ 6 | { 7 | id: "000000001", 8 | avatar: 9 | "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png", 10 | title: "你收到了 14 份新周报", 11 | datetime: "2017-08-09", 12 | type: "notification", 13 | }, 14 | { 15 | id: "000000002", 16 | avatar: 17 | "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png", 18 | title: "你推荐的 曲妮妮 已通过第三轮面试", 19 | datetime: "2017-08-08", 20 | type: "notification", 21 | }, 22 | { 23 | id: "000000003", 24 | avatar: 25 | "https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png", 26 | title: "这种模板可以区分多种通知类型", 27 | datetime: "2017-08-07", 28 | read: true, 29 | type: "notification", 30 | }, 31 | { 32 | id: "000000004", 33 | avatar: 34 | "https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png", 35 | title: "左侧图标用于区分不同的类型", 36 | datetime: "2017-08-07", 37 | type: "notification", 38 | }, 39 | { 40 | id: "000000005", 41 | avatar: 42 | "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png", 43 | title: "内容不要超过两行字,超出时自动截断", 44 | datetime: "2017-08-07", 45 | type: "notification", 46 | }, 47 | { 48 | id: "000000006", 49 | avatar: 50 | "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg", 51 | title: "曲丽丽 评论了你", 52 | description: "描述信息描述信息描述信息", 53 | datetime: "2017-08-07", 54 | type: "message", 55 | clickClose: true, 56 | }, 57 | { 58 | id: "000000007", 59 | avatar: 60 | "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg", 61 | title: "朱偏右 回复了你", 62 | description: "这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像", 63 | datetime: "2017-08-07", 64 | type: "message", 65 | clickClose: true, 66 | }, 67 | { 68 | id: "000000008", 69 | avatar: 70 | "https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg", 71 | title: "标题", 72 | description: "这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像", 73 | datetime: "2017-08-07", 74 | type: "message", 75 | clickClose: true, 76 | }, 77 | { 78 | id: "000000009", 79 | title: "任务名称", 80 | description: "任务需要在 2017-01-12 20:00 前启动", 81 | extra: "未开始", 82 | status: "todo", 83 | type: "event", 84 | }, 85 | { 86 | id: "000000010", 87 | title: "第三方紧急代码变更", 88 | description: 89 | "冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务", 90 | extra: "马上到期", 91 | status: "urgent", 92 | type: "event", 93 | }, 94 | { 95 | id: "000000011", 96 | title: "信息安全考试", 97 | description: "指派竹尔于 2017-01-09 前完成更新并发布", 98 | extra: "已耗时 8 天", 99 | status: "doing", 100 | type: "event", 101 | }, 102 | { 103 | id: "000000012", 104 | title: "ABCD 版本发布", 105 | description: 106 | "冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务", 107 | extra: "进行中", 108 | status: "processing", 109 | type: "event", 110 | }, 111 | ], 112 | }); 113 | }; 114 | 115 | export default { 116 | "GET /api/notices": getNotices, 117 | }; 118 | -------------------------------------------------------------------------------- /mock/route.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "/api/auth_routes": { 3 | "/form/advanced-form": { authority: ["admin", "user"] }, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /mock/user.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | 3 | const waitTime = (time: number = 100) => { 4 | return new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(true); 7 | }, time); 8 | }); 9 | }; 10 | 11 | async function getFakeCaptcha(req: Request, res: Response) { 12 | await waitTime(2000); 13 | return res.json("captcha-xxx"); 14 | } 15 | 16 | const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; 17 | 18 | /** 19 | * 当前用户的权限,如果为空代表没登录 20 | * current user access, if is '', user need login 21 | * 如果是 pro 的预览,默认是有权限的 22 | */ 23 | let access = 24 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === "site" ? "admin" : ""; 25 | 26 | const getAccess = () => { 27 | return access; 28 | }; 29 | 30 | // 代码中会兼容本地 service mock 以及部署站点的静态数据 31 | export default { 32 | // 支持值为 Object 和 Array 33 | "GET /api/auth/currentUser": (req: Request, res: Response) => { 34 | if (!getAccess()) { 35 | res.status(401).send({ 36 | data: { 37 | isLogin: false, 38 | }, 39 | errorCode: "401", 40 | errorMessage: "请先登录!", 41 | success: true, 42 | }); 43 | return; 44 | } 45 | res.send({ 46 | code: 200, 47 | success: true, 48 | data: { 49 | name: "admin", 50 | avatar: 51 | "https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png", 52 | userid: "00000001", 53 | email: "antdesign@alipay.com", 54 | signature: "海纳百川,有容乃大", 55 | title: "交互专家", 56 | group: "蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED", 57 | tags: [ 58 | { 59 | key: "0", 60 | label: "很有想法的", 61 | }, 62 | { 63 | key: "1", 64 | label: "专注设计", 65 | }, 66 | { 67 | key: "2", 68 | label: "辣~", 69 | }, 70 | { 71 | key: "3", 72 | label: "大长腿", 73 | }, 74 | { 75 | key: "4", 76 | label: "川妹子", 77 | }, 78 | { 79 | key: "5", 80 | label: "海纳百川", 81 | }, 82 | ], 83 | notifyCount: 12, 84 | unreadCount: 11, 85 | country: "China", 86 | access: getAccess(), 87 | geographic: { 88 | province: { 89 | label: "浙江省", 90 | key: "330000", 91 | }, 92 | city: { 93 | label: "杭州市", 94 | key: "330100", 95 | }, 96 | }, 97 | address: "西湖区工专路 77 号", 98 | phone: "0752-268888888", 99 | }, 100 | }); 101 | }, 102 | // GET POST 可省略 103 | "GET /api/users": [ 104 | { 105 | key: "1", 106 | name: "John Brown", 107 | age: 32, 108 | address: "New York No. 1 Lake Park", 109 | }, 110 | { 111 | key: "2", 112 | name: "Jim Green", 113 | age: 42, 114 | address: "London No. 1 Lake Park", 115 | }, 116 | { 117 | key: "3", 118 | name: "Joe Black", 119 | age: 32, 120 | address: "Sidney No. 1 Lake Park", 121 | }, 122 | ], 123 | "POST /api/auth/login": async (req: Request, res: Response) => { 124 | const { password, username, type } = req.body; 125 | await waitTime(2000); 126 | if (password === "123456" && username === "admin") { 127 | res.send({ 128 | code: 200, 129 | success: true, 130 | data: { 131 | status: "ok", 132 | type, 133 | currentAuthority: "admin", 134 | token: 135 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWRtaW4iLCJpYXQiOjE2NjAyMjkwNDgsImV4cCI6MTY2MDI0MDE1OX0.e8vj83BFANAl9nlOL_M5hEHD4fy60dQWJ58pMcmCLxY", 136 | }, 137 | }); 138 | access = "admin"; 139 | return; 140 | } 141 | if (password === "123456" && username === "user") { 142 | res.send({ 143 | code: 200, 144 | success: true, 145 | data: { 146 | status: "ok", 147 | type, 148 | currentAuthority: "user", 149 | token: 150 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWRtaW4iLCJpYXQiOjE2NjAyMjkwNDgsImV4cCI6MTY2MDI0MDE1OX0.e8vj83BFANAl9nlOL_M5hEHD4fy60dQWJ58pMcmCLxY", 151 | }, 152 | }); 153 | access = "user"; 154 | return; 155 | } 156 | 157 | res.send({ 158 | code: 200, 159 | success: false, 160 | data: { 161 | status: "error", 162 | type, 163 | currentAuthority: "guest", 164 | }, 165 | }); 166 | access = "guest"; 167 | }, 168 | "POST /api/login/outLogin": (req: Request, res: Response) => { 169 | access = ""; 170 | res.send({ data: {}, success: true }); 171 | }, 172 | "POST /api/register": (req: Request, res: Response) => { 173 | res.send({ status: "ok", currentAuthority: "user", success: true }); 174 | }, 175 | "GET /api/500": (req: Request, res: Response) => { 176 | res.status(500).send({ 177 | timestamp: 1513932555104, 178 | status: 500, 179 | error: "error", 180 | message: "error", 181 | path: "/base/category/list", 182 | }); 183 | }, 184 | "GET /api/404": (req: Request, res: Response) => { 185 | res.status(404).send({ 186 | timestamp: 1513932643431, 187 | status: 404, 188 | error: "Not Found", 189 | message: "No message available", 190 | path: "/base/category/list/2121212", 191 | }); 192 | }, 193 | "GET /api/403": (req: Request, res: Response) => { 194 | res.status(403).send({ 195 | timestamp: 1513932555104, 196 | status: 403, 197 | error: "Forbidden", 198 | message: "Forbidden", 199 | path: "/base/category/list", 200 | }); 201 | }, 202 | "GET /api/401": (req: Request, res: Response) => { 203 | res.status(401).send({ 204 | timestamp: 1513932555104, 205 | status: 401, 206 | error: "Unauthorized", 207 | message: "Unauthorized", 208 | path: "/base/category/list", 209 | }); 210 | }, 211 | 212 | "GET /api/login/captcha": getFakeCaptcha, 213 | }; 214 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ant-design-pro", 3 | "version": "5.2.0", 4 | "private": true, 5 | "description": "An out-of-box UI solution for enterprise applications", 6 | "scripts": { 7 | "analyze": "cross-env ANALYZE=1 umi build", 8 | "build": "umi build", 9 | "deploy": "npm run build && npm run gh-pages", 10 | "dev": "npm run start:dev", 11 | "gh-pages": "gh-pages -d dist", 12 | "i18n-remove": "pro i18n-remove --locale=zh-CN --write", 13 | "postinstall": "umi g tmp", 14 | "lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier && npm run tsc", 15 | "lint-staged": "lint-staged", 16 | "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ", 17 | "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style", 18 | "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src", 19 | "lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto", 20 | "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", 21 | "openapi": "umi openapi", 22 | "playwright": "playwright install && playwright test", 23 | "precommit": "lint-staged", 24 | "prettier": "prettier -c --write \"src/**/*\"", 25 | "serve": "umi-serve", 26 | "start": "cross-env UMI_ENV=dev umi dev", 27 | "start:dev": "cross-env REACT_APP_ENV=dev UMI_ENV=dev umi dev", 28 | "start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev", 29 | "start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev", 30 | "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev", 31 | "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev", 32 | "test": "umi test", 33 | "test:component": "umi test ./src/components", 34 | "test:e2e": "node ./tests/run-tests.js", 35 | "tsc": "tsc --noEmit" 36 | }, 37 | "lint-staged": { 38 | "**/*.less": "stylelint --syntax less", 39 | "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js", 40 | "**/*.{js,jsx,tsx,ts,less,md,json}": [ 41 | "prettier --write" 42 | ] 43 | }, 44 | "browserslist": [ 45 | "> 1%", 46 | "last 2 versions", 47 | "not ie <= 10" 48 | ], 49 | "dependencies": { 50 | "@ant-design/icons": "^4.7.0", 51 | "@ant-design/pro-descriptions": "^1.10.0", 52 | "@ant-design/pro-form": "^1.52.0", 53 | "@ant-design/pro-layout": "^6.32.0", 54 | "@ant-design/pro-table": "^2.61.0", 55 | "@umijs/route-utils": "^2.0.0", 56 | "antd": "^4.17.0", 57 | "antd-nestable": "^1.0.17", 58 | "classnames": "^2.3.0", 59 | "lodash": "^4.17.0", 60 | "moment": "^2.29.0", 61 | "omit.js": "^2.0.2", 62 | "rc-menu": "^9.1.0", 63 | "rc-util": "^5.16.0", 64 | "react": "^17.0.0", 65 | "react-dev-inspector": "^1.7.0", 66 | "react-dom": "^17.0.0", 67 | "react-helmet-async": "^1.2.0", 68 | "umi": "^3.5.0" 69 | }, 70 | "devDependencies": { 71 | "@ant-design/pro-cli": "^2.1.0", 72 | "@playwright/test": "^1.17.0", 73 | "@types/express": "^4.17.0", 74 | "@types/history": "^4.7.0", 75 | "@types/jest": "^26.0.0", 76 | "@types/lodash": "^4.14.0", 77 | "@types/react": "^17.0.0", 78 | "@types/react-dom": "^17.0.0", 79 | "@types/react-helmet": "^6.1.0", 80 | "@umijs/fabric": "^2.8.0", 81 | "@umijs/openapi": "^1.3.0", 82 | "@umijs/plugin-blocks": "^2.2.0", 83 | "@umijs/plugin-esbuild": "^1.4.0", 84 | "@umijs/plugin-openapi": "^1.3.0", 85 | "@umijs/preset-ant-design-pro": "^1.3.0", 86 | "@umijs/preset-dumi": "^1.1.0", 87 | "@umijs/preset-react": "^1.8.17", 88 | "@umijs/yorkie": "^2.0.5", 89 | "carlo": "^0.9.46", 90 | "cross-env": "^7.0.0", 91 | "cross-port-killer": "^1.3.0", 92 | "detect-installer": "^1.0.0", 93 | "enzyme": "^3.11.0", 94 | "eslint": "^7.32.0", 95 | "express": "^4.17.0", 96 | "gh-pages": "^3.2.0", 97 | "jsdom-global": "^3.0.0", 98 | "lint-staged": "^10.0.0", 99 | "mockjs": "^1.1.0", 100 | "prettier": "^2.5.0", 101 | "puppeteer-core": "^8.0.0", 102 | "stylelint": "^13.0.0", 103 | "swagger-ui-react": "^3.52.0", 104 | "typescript": "^4.5.0", 105 | "umi-serve": "^1.9.10" 106 | }, 107 | "engines": { 108 | "node": ">=10.0.0" 109 | }, 110 | "gitHooks": { 111 | "commit-msg": "fabric verify-commit" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutUI/out-admin-react/3470d8b926d17d75529268a61edcf2bf96f4ce00/public/.DS_Store -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | preview.pro.ant.design -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutUI/out-admin-react/3470d8b926d17d75529268a61edcf2bf96f4ce00/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutUI/out-admin-react/3470d8b926d17d75529268a61edcf2bf96f4ce00/public/icons/icon-128x128.png -------------------------------------------------------------------------------- /public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutUI/out-admin-react/3470d8b926d17d75529268a61edcf2bf96f4ce00/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutUI/out-admin-react/3470d8b926d17d75529268a61edcf2bf96f4ce00/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo1.svg: -------------------------------------------------------------------------------- 1 | Group 28 Copy 5Created with Sketch. -------------------------------------------------------------------------------- /public/pro_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/access.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://umijs.org/zh-CN/plugins/plugin-access 3 | * */ 4 | export default function access(initialState: { 5 | currentUser?: API.CurrentUser | undefined; 6 | }) { 7 | const { currentUser } = initialState || {}; 8 | return { 9 | canAdmin: currentUser && currentUser.name === "admin", 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import type { Settings as LayoutSettings } from "@ant-design/pro-layout"; 2 | import { SettingDrawer } from "@ant-design/pro-layout"; 3 | import { PageLoading } from "@ant-design/pro-layout"; 4 | import type { RequestConfig, RunTimeLayoutConfig } from "umi"; 5 | import { history } from "umi"; 6 | import type { RequestOptionsInit } from "umi-request"; 7 | import RightContent from "@/components/RightContent"; 8 | import Footer from "@/components/Footer"; 9 | import { currentUser as queryCurrentUser } from "./services/ant-design-pro/api"; 10 | import { LogoutOutlined } from "@ant-design/icons"; 11 | import defaultSettings from "../config/defaultSettings"; 12 | import { outLogin } from "@/services/ant-design-pro/api"; 13 | import { stringify } from "querystring"; 14 | import { notification } from "antd"; 15 | 16 | const loginPath = "/user/login"; 17 | const orderingPath = "/food"; 18 | 19 | /** 获取用户信息比较慢的时候会展示一个 loading */ 20 | export const initialStateConfig = { 21 | loading: , 22 | }; 23 | 24 | /** 25 | * 退出登录,并且将当前的 url 保存 26 | */ 27 | const loginOut = async () => { 28 | await outLogin(); 29 | const { query = {}, search, pathname } = history.location; 30 | const { redirect } = query; 31 | // Note: There may be security issues, please note 32 | if (window.location.pathname !== "/user/login" && !redirect) { 33 | history.replace({ 34 | pathname: "/user/login", 35 | search: stringify({ 36 | redirect: pathname + search, 37 | }), 38 | }); 39 | } 40 | }; 41 | 42 | /** 43 | * @see https://umijs.org/zh-CN/plugins/plugin-initial-state 44 | * */ 45 | export async function getInitialState(): Promise<{ 46 | settings?: Partial; 47 | currentUser?: API.CurrentUser; 48 | loading?: boolean; 49 | fetchUserInfo?: () => Promise; 50 | }> { 51 | const fetchUserInfo = async () => { 52 | try { 53 | const msg = await queryCurrentUser(); 54 | return msg.data; 55 | } catch (error) { 56 | history.push(loginPath); 57 | } 58 | return undefined; 59 | }; 60 | // 如果是登录页面,不执行 61 | if ( 62 | history.location.pathname !== loginPath && 63 | history.location.pathname !== orderingPath 64 | ) { 65 | const currentUser = await fetchUserInfo(); 66 | return { 67 | fetchUserInfo, 68 | currentUser, 69 | settings: defaultSettings, 70 | }; 71 | } 72 | return { 73 | fetchUserInfo, 74 | settings: defaultSettings, 75 | }; 76 | } 77 | 78 | // ProLayout 支持的api https://procomponents.ant.design/components/layout 79 | export const layout: RunTimeLayoutConfig = ({ 80 | initialState, 81 | setInitialState, 82 | }) => { 83 | const updateSize = document.querySelector("body")?.offsetWidth; 84 | return { 85 | iconfontUrl: "//at.alicdn.com/t/font_3278601_3g0uzbi4yim.js", 86 | menuHeaderRender: (logo) => ( 87 | <> 88 | {logo} 89 | {initialState?.currentUser?.name} 90 | 91 | ), 92 | links: [ 93 | { 96 | setInitialState((s) => ({ ...s, currentUser: undefined })); 97 | loginOut(); 98 | }} 99 | > 100 | 101 | 退出 102 | , 103 | ], 104 | headerRender: updateSize && updateSize < 765, 105 | breakpoint: "xs", 106 | //collapsed: false, 107 | //collapsedButtonRender: false, 108 | rightContentRender: () => , 109 | disableContentMargin: false, 110 | footerRender: () =>