├── .editorconfig ├── .gitignore ├── README.md ├── build.sh ├── config ├── config.ts ├── default-settings.ts ├── proxy.ts └── routes.ts ├── docker └── nginx.conf ├── jsconfig.json ├── mock ├── dashboard.ts ├── permission.ts ├── user.ts └── utils.ts ├── now.json ├── package.json ├── renovate.json ├── src ├── app.tsx ├── assets │ └── logo.svg ├── authority.ts ├── common │ ├── constant │ │ └── index.ts │ └── types │ │ ├── api │ │ ├── base.d.ts │ │ ├── permission.d.ts │ │ └── user.d.ts │ │ ├── global.d.ts │ │ └── login.ts ├── components │ ├── footer │ │ └── index.tsx │ ├── header-dropdown │ │ ├── index.less │ │ └── index.tsx │ ├── header-search │ │ ├── index.less │ │ └── index.tsx │ ├── page-loading │ │ └── index.tsx │ └── right-content │ │ ├── avatar-dropdown.tsx │ │ ├── index.less │ │ └── index.tsx ├── config │ └── index.ts ├── global.less ├── layouts │ ├── user-layout.less │ └── user-layout.tsx ├── locales │ ├── en-US.ts │ ├── en-US │ │ └── menu.ts │ ├── zh-CN.ts │ └── zh-CN │ │ └── menu.ts ├── models │ └── login.ts ├── pages │ ├── 404.tsx │ ├── dashboard │ │ ├── components │ │ │ ├── introduce-row │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── proportion-sales │ │ │ │ └── index.tsx │ │ │ ├── sales-card │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── top-search │ │ │ │ └── index.tsx │ │ │ └── trend │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ └── index.tsx │ ├── exception │ │ ├── 403.tsx │ │ ├── 404.tsx │ │ └── 500.tsx │ ├── libraries │ │ ├── amap │ │ │ ├── components │ │ │ │ ├── cluster │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── marker │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── styles.less │ │ └── watermark │ │ │ └── index.tsx │ ├── login │ │ ├── components │ │ │ ├── captcha.tsx │ │ │ ├── context.tsx │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── item.tsx │ │ │ ├── map.tsx │ │ │ ├── submit.tsx │ │ │ └── tab.tsx │ │ ├── index.tsx │ │ └── style.less │ ├── nested │ │ ├── menu1 │ │ │ ├── menu1-1.tsx │ │ │ ├── menu1-2 │ │ │ │ ├── menu1-2-1.tsx │ │ │ │ └── menu1-2-2.tsx │ │ │ └── menu1-3.tsx │ │ └── menu2 │ │ │ └── index.tsx │ ├── permission │ │ ├── action │ │ │ ├── components │ │ │ │ └── operation.tsx │ │ │ └── index.tsx │ │ ├── button │ │ │ └── index.tsx │ │ ├── constant.ts │ │ ├── page │ │ │ └── index.tsx │ │ └── policy │ │ │ └── index.tsx │ ├── register │ │ ├── index.tsx │ │ └── style.less │ └── system │ │ ├── role │ │ └── index.tsx │ │ └── user │ │ └── index.tsx ├── services │ ├── dashboard.ts │ ├── login.ts │ ├── permission.ts │ └── user.ts ├── typings.d.ts └── utils │ ├── cookie.ts │ ├── date.ts │ └── index.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | /.vscode 12 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-error.log 17 | 18 | /coverage 19 | .idea 20 | yarn.lock 21 | package-lock.json 22 | *bak 23 | .vscode 24 | 25 | # visual studio code 26 | .history 27 | *.log 28 | functions/* 29 | .temp/** 30 | 31 | # umi 32 | .umi 33 | .umi-production 34 | 35 | # screenshot 36 | screenshot 37 | .firebase 38 | .eslintcache 39 | 40 | build 41 | *.less.d.ts 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

React Admin Template

8 | 9 |
10 | 开箱即用的中台前端/设计解决方案。 11 |
12 | 13 | ## ✨ 特性 14 | 15 | - 🛡 **TypeScript**: 应用程序级 JavaScript 的语言 16 | - 💎 **优雅美观**:基于 Ant Design 体系精心设计 17 | - 🚀 **最新技术栈**:使用 React/umi/antd 等前端前沿技术开发 18 | - 🌐 **国际化**:内建业界通用的国际化方案 19 | - 🔢 **Mock 数据**:实用的本地数据调试方案 20 | - ⚙️ **最佳实践**:良好的工程实践助您持续产出高质量代码 21 | - 🔐 **优秀的权限设计**:目前能找到的最好的权限实现方案 22 | 23 | ## 🎉 推荐 24 | 25 | - 微前端版本 [micro-frontends-template](https://github.com/pansyjs/micro-frontends-template) 正在同步开发中... 26 | - 好用的水印组件 [watermark](https://github.com/pansyjs/watermark) 27 | 28 | 29 | ## 📜 目录 30 | 31 | ``` 32 | ├── config # umi 相关配置 33 | ├── docker # docker 相关配置 34 | ├── mock # 本地模拟数据 35 | ├── public # 静态资源 36 | ├── src # 源代码 37 | │ ├── assets # 本地静态资源 38 | │ ├── common # 类型定义、常量 39 | │ ├── components # 全局公用组件 40 | │ ├── config # 全局配置 41 | │ ├── layouts # 布局文件 42 | │ ├── locales # 国际化资源 43 | │ ├── models # 路由 44 | │ ├── pages # 业务页面入口和常用模板 45 | │ ├── services # 所有请求 46 | │ ├── utils # 全局公用方法 47 | │ ├── app.tsx # 运行时配置文件 48 | │ ├── authority.ts # 权限定义文件 49 | │ ├── global.less # 全局样式 50 | │ └── typings.d.ts # 补充类型定义 51 | ├── package.json # package.json 52 | └── tsconfig.json # tsconfig.json 53 | ``` 54 | 55 | ## 🔐 关于权限 56 | 57 | 基于 [umi-plugin-authority](https://github.com/alitajs/umi-plugins/tree/master/packages/umi-plugin-authority) 提供权限功能,暴露 `useAuthority` hooks 和 `Authority` 组件实现权限控制的能力 58 | 59 | 使用示例如下 60 | 61 | ```tsx 62 | import React from 'react'; 63 | import { useAuthority, Authority } from 'umi'; 64 | 65 | const PageA = props => { 66 | const { foo } = props; 67 | const { combinationVerify } = useAuthority(); 68 | 69 | // 使用 hooks 提供的能力 70 | if (combinationVerify('module1/action1')) { 71 | // 存在 module1/action1 权限,则... 72 | } 73 | 74 | return ( 75 |
76 | {/** 指定需要验证的权限 */} 77 | Can not read foo content.
} 80 | > 81 | Foo content. 82 | 83 | {/** 直接指定权限 */} 84 | Can not update foo.} 87 | > 88 | Update foo. 89 | 90 | {/** 复杂的校验 */} 91 | Can not update foo.} 94 | > 95 | Update foo. 96 | 97 | {/** children 为function */} 98 | Can not delete foo.} 101 | > 102 | {(isMatch) => 权限校验结果: {isMatch}} 103 | 104 | 105 | ); 106 | }; 107 | ``` 108 | 109 | ## ⌨️ 本地开发 110 | 111 | ```sh 112 | # 克隆项目到本地 113 | git clone git@github.com:ts-react/react-admin-template.git 114 | 115 | # 切换到项目目录 116 | cd ./react-admin-template 117 | 118 | # 安装依赖 119 | yarn 120 | 121 | # 启动服务 122 | npm run start 123 | ``` 124 | 125 | ## 🖥 支持环境 126 | 127 | 现代浏览器及 IE11。 128 | 129 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | 130 | | --- | --- | --- | --- | --- | 131 | | IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | 132 | 133 | ## 👥 社区互助 134 | 135 | | Github Issue | 钉钉群 | 微信群 | 136 | | ------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | 137 | | [issues](https://github.com/ts-react/react-admin-template/issues) | | | 138 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | yarn 3 | yarn build 4 | -------------------------------------------------------------------------------- /config/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | import { routes } from './routes'; 3 | import defaultSettings from './default-settings'; 4 | import proxy from './proxy'; 5 | 6 | const { REACT_APP_ENV } = process.env; 7 | 8 | export default defineConfig({ 9 | hash: true, 10 | routes, 11 | targets: { 12 | ie: 11, 13 | }, 14 | antd: { 15 | config: {} 16 | }, 17 | layout: { 18 | name: defaultSettings.title, 19 | locale: true, 20 | siderWidth: 240 21 | }, 22 | locale: { 23 | default: 'zh-CN', 24 | antd: true, 25 | baseNavigator: true, 26 | libraryName: '@alitajs/antd-plus' 27 | }, 28 | webpack5: { 29 | lazyCompilation: {}, 30 | }, 31 | consoleVersion: { 32 | projectName: 'admin-template' 33 | }, 34 | dynamicImport: { 35 | loading: '@/components/page-loading/index', 36 | }, 37 | theme: { 38 | 'primary-color': defaultSettings.primaryColor, 39 | }, 40 | cssModulesTypescriptLoader: {}, 41 | ignoreMomentLocale: true, 42 | // proxy: proxy[REACT_APP_ENV || 'dev'], 43 | }) 44 | -------------------------------------------------------------------------------- /config/default-settings.ts: -------------------------------------------------------------------------------- 1 | import { Settings } from '@ant-design/pro-layout'; 2 | 3 | const settings: Settings = { 4 | navTheme: 'dark', 5 | // 拂晓蓝 6 | primaryColor: '#409EFF', 7 | layout: 'side', 8 | contentWidth: 'Fluid', 9 | fixedHeader: false, 10 | fixSiderbar: true, 11 | colorWeak: false, 12 | menu: { 13 | locale: true, 14 | }, 15 | title: 'React Admin', 16 | iconfontUrl: '', 17 | }; 18 | 19 | export default settings 20 | -------------------------------------------------------------------------------- /config/proxy.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | dev: { 3 | '/api/': { 4 | target: 'https://preview.pro.ant.design', 5 | changeOrigin: true, 6 | pathRewrite: { '^': '' }, 7 | }, 8 | }, 9 | test: { 10 | '/api/': { 11 | target: 'https://preview.pro.ant.design', 12 | changeOrigin: true, 13 | pathRewrite: { '^': '' }, 14 | }, 15 | }, 16 | pre: { 17 | '/api/': { 18 | target: 'your pre url', 19 | changeOrigin: true, 20 | pathRewrite: { '^': '' }, 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /config/routes.ts: -------------------------------------------------------------------------------- 1 | export const routes = [ 2 | { 3 | path: '/login', 4 | component: 'login', 5 | layout: false 6 | }, 7 | { 8 | path: '/register', 9 | component: 'register', 10 | layout: false 11 | }, 12 | { 13 | path: '/dashboard', 14 | component: 'dashboard', 15 | menu: { 16 | name: '首页', 17 | icon: 'dashboard', 18 | }, 19 | }, 20 | { 21 | path: '/', 22 | redirect: '/dashboard', 23 | }, 24 | { 25 | path: '/nested', 26 | menu: { 27 | name: '路由嵌套', 28 | icon: 'bars', 29 | }, 30 | routes: [ 31 | { 32 | path: '/nested/menu1', 33 | menu: { 34 | name: '菜单1' 35 | }, 36 | routes: [ 37 | { 38 | path: '/nested/menu1/menu1-1', 39 | menu: { 40 | name: '菜单1-1' 41 | }, 42 | component: '@/pages/nested/menu1/menu1-1' 43 | }, 44 | { 45 | path: '/nested/menu1/menu1-2', 46 | menu: { 47 | name: '菜单1-2' 48 | }, 49 | routes: [ 50 | { 51 | path: '/nested/menu1/menu1-2', 52 | redirect: '/nested/menu1/menu1-2/menu1-2-1', 53 | }, 54 | { 55 | path: '/nested/menu1/menu1-2/menu1-2-1', 56 | menu: { 57 | name: '菜单1-2-1' 58 | }, 59 | component: '@/pages/nested/menu1/menu1-2/menu1-2-1' 60 | }, 61 | { 62 | path: '/nested/menu1/menu1-2/menu1-2-2', 63 | menu: { 64 | name: '菜单1-2-2' 65 | }, 66 | component: '@/pages/nested/menu1/menu1-2/menu1-2-2' 67 | } 68 | ] 69 | }, 70 | { 71 | path: '/nested/menu1/menu1-3', 72 | menu: { 73 | name: '菜单1-3' 74 | }, 75 | component: '@/pages/nested/menu1/menu1-3' 76 | }, 77 | { 78 | path: '/nested/menu1', 79 | redirect: '/nested/menu1/menu1-1', 80 | } 81 | ] 82 | }, 83 | { 84 | path: '/nested/menu2', 85 | menu: { 86 | name: '菜单2' 87 | }, 88 | component: '@/pages/nested/menu2' 89 | }, 90 | { 91 | path: '/nested', 92 | redirect: '/nested/menu1', 93 | } 94 | ] 95 | }, 96 | { 97 | path: '/exception', 98 | menu: { 99 | name: '异常页', 100 | icon: 'warning', 101 | }, 102 | routes: [ 103 | { 104 | path: '/exception/403', 105 | menu: { 106 | name: '403' 107 | }, 108 | component: '@/pages/exception/403' 109 | }, 110 | { 111 | path: '/exception/404', 112 | menu: { 113 | name: '404' 114 | }, 115 | component: '@/pages/exception/404' 116 | }, 117 | { 118 | path: '/exception/500', 119 | menu: { 120 | name: '500' 121 | }, 122 | component: '@/pages/exception/500' 123 | }, 124 | ] 125 | }, 126 | { 127 | path: '/libraries', 128 | menu: { 129 | name: '组件', 130 | icon: 'appstore', 131 | }, 132 | routes: [ 133 | { 134 | path: '/libraries/amap', 135 | menu: { 136 | name: '高德地图' 137 | }, 138 | component: '@/pages/libraries/amap' 139 | }, 140 | { 141 | path: '/libraries/watermark', 142 | menu: { 143 | name: '水印组件' 144 | }, 145 | component: '@/pages/libraries/watermark' 146 | } 147 | ] 148 | }, 149 | { 150 | path: '/permission', 151 | menu: { 152 | name: '权限管理', 153 | icon: 'lock', 154 | }, 155 | routes: [ 156 | { 157 | path: '/permission', 158 | redirect: '/permission/action', 159 | }, 160 | { 161 | path: '/permission/page', 162 | menu: { 163 | name: '页面权限测试' 164 | }, 165 | component: '@/pages/permission/page' 166 | }, 167 | { 168 | path: '/permission/button', 169 | menu: { 170 | name: '按钮权限测试' 171 | }, 172 | component: '@/pages/permission/button' 173 | }, 174 | { 175 | path: '/permission/action', 176 | menu: { 177 | name: '操作管理' 178 | }, 179 | authority: 'permission:actionView', 180 | component: '@/pages/permission/action' 181 | }, 182 | { 183 | path: '/permission/policy', 184 | menu: { 185 | name: '策略管理' 186 | }, 187 | authority: 'permission:policyView', 188 | component: '@/pages/permission/policy' 189 | }, 190 | ] 191 | }, 192 | { 193 | path: '/system', 194 | menu: { 195 | name: '系统管理', 196 | icon: 'desktop', 197 | }, 198 | routes: [ 199 | { 200 | path: '/system', 201 | redirect: '/system/user', 202 | }, 203 | { 204 | path: '/system/user', 205 | menu: { 206 | name: '用户管理' 207 | }, 208 | component: '@/pages/system/user' 209 | }, 210 | { 211 | path: '/system/role', 212 | menu: { 213 | name: '角色管理' 214 | }, 215 | component: '@/pages/system/role' 216 | } 217 | ] 218 | }, 219 | { 220 | component: './404', 221 | } 222 | ] 223 | -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | # gzip config 4 | gzip on; 5 | gzip_min_length 1k; 6 | gzip_comp_level 9; 7 | gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; 8 | gzip_vary on; 9 | gzip_disable "MSIE [1-6]\."; 10 | 11 | root /usr/share/nginx/html; 12 | include /etc/nginx/mime.types; 13 | location / { 14 | try_files $uri $uri/ /index.html; 15 | } 16 | location /api { 17 | proxy_pass https://proapi.azurewebsites.net; 18 | proxy_set_header X-Forwarded-Proto $scheme; 19 | proxy_set_header X-Real-IP $remote_addr; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mock/dashboard.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { Request, Response } from 'express'; 3 | import { packResult } from './utils'; 4 | 5 | // mock data 6 | const visitData: { date: string; value: number }[] = []; 7 | const beginDay = new Date().getTime(); 8 | 9 | const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]; 10 | for (let i = 0; i < fakeY.length; i += 1) { 11 | visitData.push({ 12 | date: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), 13 | value: fakeY[i], 14 | }); 15 | } 16 | 17 | const salesData: { date: string; value: number }[] = []; 18 | for (let i = 0; i < 12; i += 1) { 19 | salesData.push({ 20 | date: `${i + 1}月`, 21 | value: Math.floor(Math.random() * 1000) + 200, 22 | }); 23 | } 24 | 25 | const fetchChartData = (_: Request, res: Response) => { 26 | return res.json(packResult({ data: { visitData, salesData } })); 27 | } 28 | 29 | export default { 30 | 'GET /api/dashboard/chartData': fetchChartData, 31 | }; 32 | -------------------------------------------------------------------------------- /mock/permission.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { mock } from 'better-mock'; 3 | import { Request, Response } from 'express'; 4 | import { packResult } from './utils'; 5 | 6 | let actions: API.PermissionActionData[] = []; 7 | 8 | for (let i = 0; i < 11; i++) { 9 | actions.push( 10 | mock({ 11 | id: '@guid', 12 | module: 'permission', 13 | code: '@string(8)', 14 | name: '@ctitle(4)', 15 | remark: '@cparagraph(1)', 16 | createdAt: moment().valueOf(), 17 | updatedAt: moment().valueOf(), 18 | }), 19 | ); 20 | } 21 | 22 | const fetchActionList = (req: Request, res: Response) => { 23 | const { page = 1, size = 20 } = req.query; 24 | 25 | const pageList = actions.filter((item, index) => { 26 | return index < +size * +page && index >= +size * (+page - 1); 27 | }); 28 | 29 | return res.json( 30 | packResult({ 31 | data: { 32 | list: pageList, 33 | total: actions.length 34 | } 35 | }) 36 | ); 37 | } 38 | 39 | export default { 40 | 'GET /api/permission/action/list': fetchActionList, 41 | }; 42 | -------------------------------------------------------------------------------- /mock/user.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { packResult } from './utils'; 3 | 4 | const permissionCodes = [ 5 | { group: 'dashboard', actions: ['view'] }, 6 | { 7 | group: 'permission', 8 | actions: [ 9 | 'view', 10 | 'policyAdd', 11 | 'policyDelete', 12 | 'policyModify', 13 | 'policyView', 14 | 'actionAdd', 15 | 'actionDelete', 16 | 'actionModify', 17 | 'actionView', 18 | ] 19 | } 20 | ] 21 | 22 | function fetchCaptcha(req: Request, res: Response) { 23 | res.send(packResult()); 24 | } 25 | 26 | function fetchCurrentUser(req: Request, res: Response) { 27 | const authorization = req.headers?.authorization; 28 | const token = authorization?.split(' ')?.[1]; 29 | 30 | if (token !== 'admin' && token !== 'user') { 31 | res.status(401).send(packResult({ data: {}, code: 401 })) 32 | } 33 | 34 | const data = { 35 | name: 'Serati Ma', 36 | avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png', 37 | userid: '00000001', 38 | email: 'antdesign@alipay.com', 39 | signature: '海纳百川,有容乃大', 40 | title: '交互专家', 41 | group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED', 42 | // 所有的权限 43 | permissionCodes, 44 | // 赋予的权限 45 | access: [ 46 | { group: 'module1', actions: '*' }, 47 | { group: 'module2', actions: ['action1'] } 48 | ], 49 | tags: [ 50 | { 51 | key: '0', 52 | label: '很有想法的', 53 | }, 54 | { 55 | key: '1', 56 | label: '专注设计', 57 | }, 58 | { 59 | key: '2', 60 | label: '辣~', 61 | }, 62 | { 63 | key: '3', 64 | label: '大长腿', 65 | }, 66 | { 67 | key: '4', 68 | label: '川妹子', 69 | }, 70 | { 71 | key: '5', 72 | label: '海纳百川', 73 | }, 74 | ], 75 | notifyCount: 12, 76 | unreadCount: 11, 77 | country: 'China', 78 | geographic: { 79 | province: { 80 | label: '浙江省', 81 | key: '330000', 82 | }, 83 | city: { 84 | label: '杭州市', 85 | key: '330100', 86 | }, 87 | }, 88 | address: '西湖区工专路 77 号', 89 | phone: '0752-268888888', 90 | } 91 | 92 | res.send(packResult({ data })); 93 | }; 94 | 95 | function fetchLogin(req: Request, res: Response) { 96 | const { password, username, type } = req.body; 97 | if (password === '123456' && username === 'admin') { 98 | res.send(packResult( 99 | { data: { type, token: 'admin', } 100 | })); 101 | return; 102 | } 103 | if (password === '123456' && username === 'user') { 104 | res.send(packResult({ 105 | data: { type, token: 'user', } 106 | })); 107 | return; 108 | } 109 | if (type === 'mobile') { 110 | res.send(packResult({ 111 | data: { type, token: 'admin'} })); 112 | return; 113 | } 114 | 115 | res.send(packResult({ 116 | data: { 117 | type 118 | }, 119 | code: 10010, 120 | message: '用户名或密码不正确' 121 | })); 122 | } 123 | 124 | function fetchLogout(req: Request, res: Response) { 125 | res.send(packResult()); 126 | } 127 | 128 | export default { 129 | 'GET /api/user/captcha': fetchCaptcha, 130 | 131 | 'POST /api/user/login': fetchLogin, 132 | 133 | 'GET /api/user/current': fetchCurrentUser, 134 | 135 | 'POST /api/user/logout': fetchLogout 136 | } 137 | -------------------------------------------------------------------------------- /mock/utils.ts: -------------------------------------------------------------------------------- 1 | interface ResultParams { 2 | data?: any; 3 | code?: number; 4 | message?: string; 5 | [key: string]: any 6 | } 7 | 8 | /** 9 | * 包裹请求数据 10 | * @param data 需要返回的数据 11 | * @param code 返回的状态码 12 | * @param message 返回的状态码描述 13 | */ 14 | export function packResult(params?: ResultParams) { 15 | const { 16 | data = undefined, 17 | code = 200, 18 | message = 'success', 19 | ...reset 20 | } = params || {}; 21 | 22 | return { 23 | data, 24 | code, 25 | message, 26 | ...reset 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-admin", 3 | "version": 2, 4 | "builds": [ 5 | { 6 | "src": "build.sh", 7 | "use": "@now/static-build", 8 | "config": { 9 | "distDir": "dist" 10 | } 11 | } 12 | ], 13 | "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-admin", 3 | "version": "2.0.0", 4 | "description": "react development template", 5 | "main": "index.js", 6 | "repository": "git@github.com:pansyjs/react-admin.git", 7 | "author": "kang ", 8 | "license": "MIT", 9 | "scripts": { 10 | "analyze": "cross-env ANALYZE=1 umi build", 11 | "start": "npm run start:dev", 12 | "build": "umi build", 13 | "test": "walrus test", 14 | "prettier": "prettier -c --write \"src/**/*\"", 15 | "start:dev": "cross-env REACT_APP_ENV=dev umi dev", 16 | "start:no-mock": "cross-env MOCK=none umi dev", 17 | "lint:es": "walrus eslint", 18 | "lint:style": "walrus stylelint --fix 'src/**/*.less' --syntax less", 19 | "lint:commit": "walrus commitlint --env HUSKY_GIT_PARAMS" 20 | }, 21 | "dependencies": { 22 | "@alitajs/antd-plus": "^2.5.0", 23 | "@ant-design/icons": "^4.7.0", 24 | "@ant-design/pro-card": "^1.18.30", 25 | "@ant-design/pro-layout": "^6.32.12", 26 | "@ant-design/pro-table": "^2.63.4", 27 | "@formily/antd": "^2.0.10", 28 | "@formily/antd-components": "^1.3.17", 29 | "@formily/core": "^2.0.10", 30 | "@formily/react": "^2.0.10", 31 | "@pansy/policy": "^0.5.0", 32 | "@pansy/react-amap": "2.12.3", 33 | "@pansy/react-charts": "^1.0.0", 34 | "@pansy/react-hooks": "^0.9.2", 35 | "@pansy/react-watermark": "^3.1.8", 36 | "antd": "^4.18.6", 37 | "classnames": "^2.3.1", 38 | "moment": "^2.29.1", 39 | "numeral": "^2.0.6", 40 | "react": "^17.0.2", 41 | "react-dom": "^17.0.2", 42 | "react-helmet-async": "^1.2.2", 43 | "styled-components": "^5.3.3", 44 | "umi": "^3.5.20", 45 | "umi-request": "^1.4.0", 46 | "use-merge-value": "^1.0.2" 47 | }, 48 | "devDependencies": { 49 | "@alitajs/umi-plugin-antd-plus": "0.2.0", 50 | "@alitajs/umi-plugin-console-version": "0.4.0", 51 | "@alitajs/umi-preset-react": "0.3.0", 52 | "@types/classnames": "2.3.1", 53 | "@types/numeral": "2.0.2", 54 | "@types/react": "17.0.39", 55 | "@types/react-dom": "17.0.11", 56 | "@types/styled-components": "5.1.22", 57 | "@walrus/cli": "1.3.4", 58 | "@walrus/plugin-release": "1.14.3", 59 | "@walrus/preset-lint": "1.1.8", 60 | "better-mock": "0.3.2", 61 | "cross-env": "7.0.3", 62 | "husky": "5.2.0", 63 | "typescript": "4.5.5" 64 | }, 65 | "husky": { 66 | "hooks": { 67 | "pre-commit": "yarn prettier", 68 | "commit-msg": "yarn lint:commit" 69 | } 70 | }, 71 | "browserslist": [ 72 | "> 1%", 73 | "last 2 versions", 74 | "not ie < 10" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { notification } from 'antd'; 3 | import { ResponseError } from 'umi-request'; 4 | import { ConfigProviderProps } from 'antd/es/config-provider'; 5 | import { BasicLayoutProps, Settings as LayoutSettings } from '@ant-design/pro-layout'; 6 | import { history, RequestConfig } from 'umi'; 7 | import { fetchCurrent } from '@/services/user'; 8 | import RightContent from '@/components/right-content'; 9 | import Footer from '@/components/footer'; 10 | import { NO_LOGIN_WHITELIST } from '@/config'; 11 | import { getCookie, removeCookie } from '@/utils/cookie'; 12 | import logo from '@/assets/logo.svg'; 13 | import defaultSettings from '../config/default-settings'; 14 | 15 | export async function getInitialState(): Promise<{ 16 | settings?: LayoutSettings; 17 | currentUser?: API.CurrentUser; 18 | fetchUserInfo: () => Promise; 19 | }> { 20 | const fetchUserInfo = async () => { 21 | try { 22 | const { data } = await fetchCurrent(); 23 | return data; 24 | } catch (error) { 25 | history.push('/login'); 26 | } 27 | return undefined; 28 | }; 29 | // 如果是登录页面,不执行 30 | if (NO_LOGIN_WHITELIST.indexOf(history.location.pathname) === -1) { 31 | const currentUser = await fetchUserInfo(); 32 | return { 33 | fetchUserInfo, 34 | currentUser, 35 | settings: defaultSettings, 36 | }; 37 | } 38 | return { 39 | fetchUserInfo, 40 | settings: defaultSettings, 41 | }; 42 | } 43 | 44 | export const layout = ({ 45 | initialState, 46 | }: { 47 | initialState: { settings?: LayoutSettings; currentUser?: API.CurrentUser }; 48 | }): BasicLayoutProps => { 49 | return { 50 | rightContentRender: () => , 51 | disableContentMargin: false, 52 | footerRender: () =>