├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
└── workflows
│ └── rebase.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .stylelintrc.js
├── README.md
├── config
├── config.js
├── defaultSettings.js
├── plugin.config.js
├── route.config.js
└── themePluginConfig.js
├── jest-puppeteer.config.js
├── jest.config.js
├── jsconfig.json
├── package.json
├── public
├── favicon.png
└── icons
│ ├── icon-128x128.png
│ ├── icon-192x192.png
│ └── icon-512x512.png
├── screenshot_qq.jpeg
├── src
├── api.jsx
├── assets
│ └── logo.svg
├── components
│ ├── Authorized
│ │ ├── Authorized.jsx
│ │ ├── AuthorizedRoute.jsx
│ │ ├── CheckPermissions.jsx
│ │ ├── PromiseRender.jsx
│ │ ├── Secured.jsx
│ │ ├── index.jsx
│ │ └── renderAuthorize.js
│ ├── GlobalHeader
│ │ ├── AvatarDropdown.jsx
│ │ ├── NoticeIconView.jsx
│ │ ├── RightContent.jsx
│ │ └── index.less
│ ├── HeaderDropdown
│ │ ├── index.jsx
│ │ └── index.less
│ ├── HeaderSearch
│ │ ├── index.jsx
│ │ └── index.less
│ ├── NoticeIcon
│ │ ├── NoticeList.jsx
│ │ ├── NoticeList.less
│ │ ├── index.jsx
│ │ └── index.less
│ ├── PageLoading
│ │ └── index.jsx
│ └── SelectLang
│ │ ├── index.jsx
│ │ └── index.less
├── e2e
│ ├── __mocks__
│ │ └── antd-pro-merge-less.js
│ ├── baseLayout.e2e.js
│ └── topMenu.e2e.js
├── global.jsx
├── global.less
├── layouts
│ ├── BasicLayout.jsx
│ ├── BlankLayout.jsx
│ ├── SecurityLayout.jsx
│ ├── UserLayout.jsx
│ └── UserLayout.less
├── locales
│ ├── en-US.js
│ ├── en-US
│ │ ├── component.js
│ │ ├── globalHeader.js
│ │ ├── menu.js
│ │ ├── pwa.js
│ │ ├── role.js
│ │ ├── settingDrawer.js
│ │ ├── settings.js
│ │ └── user.js
│ ├── pt-BR.js
│ ├── pt-BR
│ │ ├── component.js
│ │ ├── globalHeader.js
│ │ ├── menu.js
│ │ ├── pwa.js
│ │ ├── settingDrawer.js
│ │ └── settings.js
│ ├── zh-CN.js
│ ├── zh-CN
│ │ ├── component.js
│ │ ├── globalHeader.js
│ │ ├── menu.js
│ │ ├── pwa.js
│ │ ├── role.js
│ │ ├── settingDrawer.js
│ │ ├── settings.js
│ │ └── user.js
│ ├── zh-TW.js
│ └── zh-TW
│ │ ├── component.js
│ │ ├── globalHeader.js
│ │ ├── menu.js
│ │ ├── pwa.js
│ │ ├── settingDrawer.js
│ │ └── settings.js
├── manifest.json
├── models
│ ├── global.js
│ ├── login.js
│ ├── menu.js
│ ├── role.js
│ ├── setting.js
│ └── user.js
├── pages
│ ├── 404.jsx
│ ├── Authorized.jsx
│ ├── Welcome.jsx
│ ├── Welcome.less
│ ├── document.ejs
│ ├── system
│ │ ├── menu
│ │ │ ├── components
│ │ │ │ ├── MenuAction
│ │ │ │ │ ├── AddDialog.jsx
│ │ │ │ │ ├── EditableCell.jsx
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── MenuEditor.jsx
│ │ │ │ ├── MenuResource
│ │ │ │ │ ├── AddDialog.jsx
│ │ │ │ │ ├── EditableCell.jsx
│ │ │ │ │ └── index.jsx
│ │ │ │ └── StandardTable
│ │ │ │ │ ├── index.jsx
│ │ │ │ │ └── index.less
│ │ │ ├── data.d.ts
│ │ │ ├── index.jsx
│ │ │ ├── style.less
│ │ │ └── utils
│ │ │ │ └── utils.less
│ │ └── role
│ │ │ ├── components
│ │ │ ├── RoleEditor.jsx
│ │ │ ├── RoleMenu
│ │ │ │ ├── EditableCell.jsx
│ │ │ │ └── index.jsx
│ │ │ └── StandardTable
│ │ │ │ ├── index.jsx
│ │ │ │ └── index.less
│ │ │ ├── data.d.ts
│ │ │ ├── index.jsx
│ │ │ ├── style.less
│ │ │ └── utils
│ │ │ └── utils.less
│ └── user
│ │ ├── components
│ │ └── UpdatePasswordDialog.jsx
│ │ ├── list
│ │ ├── components
│ │ │ ├── RoleSelect.jsx
│ │ │ ├── StandardTable
│ │ │ │ ├── index.jsx
│ │ │ │ └── index.less
│ │ │ └── UserEditor.jsx
│ │ ├── data.d.ts
│ │ ├── index.jsx
│ │ ├── style.less
│ │ └── utils
│ │ │ └── utils.less
│ │ └── login
│ │ ├── components
│ │ └── Login
│ │ │ ├── LoginContext.jsx
│ │ │ ├── LoginItem.jsx
│ │ │ ├── LoginSubmit.jsx
│ │ │ ├── LoginTab.jsx
│ │ │ ├── index.jsx
│ │ │ ├── index.less
│ │ │ └── map.jsx
│ │ ├── index.jsx
│ │ ├── locales
│ │ ├── en-US.js
│ │ ├── zh-CN.js
│ │ └── zh-TW.js
│ │ └── style.less
├── service-worker.js
├── services
│ ├── login.js
│ ├── menu.js
│ ├── role.js
│ └── user.js
└── utils
│ ├── Authorized.js
│ ├── authority.js
│ ├── authority.test.js
│ ├── request.js
│ ├── store.js
│ ├── utils.js
│ ├── utils.less
│ └── utils.test.js
└── tests
└── run-tests.js
/.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 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /lambda/
2 | /scripts
3 | /config
4 | .history
--------------------------------------------------------------------------------
/.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 | },
7 | };
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '报告Bug 🐛'
3 | about: 报告 Ant Design Pro 的 bug
4 | title: '🐛[BUG]'
5 | labels: '🐛bug'
6 | assignees: ''
7 | ---
8 |
9 | ### 🐛 bug 描述
10 |
11 |
14 |
15 | ### 📷 复现步骤
16 |
17 |
20 |
21 | ### 🏞 期望结果
22 |
23 |
26 |
27 | ### 💻 复现代码
28 |
29 |
32 |
33 | ### © 版本信息
34 |
35 | - Ant Design Pro 版本: [e.g. 4.0.0]
36 | - umi 版本
37 | - 浏览器环境
38 | - 开发环境 [e.g. mac OS]
39 |
40 | ### 🚑 其他信息
41 |
42 |
45 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '功能需求 ✨'
3 | about: 对 Ant Design Pro 的需求或建议
4 | title: '👑 [需求]'
5 | labels: '👑Feature Request'
6 | assignees: ''
7 | ---
8 |
9 | ### 🥰 需求描述
10 |
11 |
14 |
15 | ### 🧐 解决方案
16 |
17 |
20 |
21 | ### 🚑 其他信息
22 |
23 |
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '疑问或需要帮助 ❓'
3 | about: 对 Ant Design Pro 使用的疑问或需要帮助
4 | title: '🧐[问题]'
5 | labels: '🧐question'
6 | assignees: ''
7 | ---
8 |
9 | ### 🧐 问题描述
10 |
11 |
14 |
15 | ### 💻 示例代码
16 |
17 |
20 |
21 | ### 🚑 其他信息
22 |
23 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/rebase.yml:
--------------------------------------------------------------------------------
1 | on:
2 | issue_comment:
3 | types: [created]
4 | name: Automatic Rebase
5 | jobs:
6 | rebase:
7 | name: Rebase
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@master
11 | - name: Automatic Rebase
12 | uses: cirrus-actions/rebase@master
13 | env:
14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.svg
2 | package.json
3 | .umi
4 | .umi-production
5 | /dist
6 | .dockerignore
7 | .DS_Store
8 | .eslintignore
9 | *.png
10 | *.toml
11 | docker
12 | .editorconfig
13 | Dockerfile*
14 | .gitignore
15 | .prettierignore
16 | LICENSE
17 | .eslintcache
18 | *.lock
19 | yarn-error.log
20 | .history
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | const fabric = require('@umijs/fabric');
2 |
3 | module.exports = {
4 | ...fabric.prettier,
5 | };
6 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | const fabric = require('@umijs/fabric');
2 |
3 | module.exports = {
4 | ...fabric.stylelint,
5 | };
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gin Admin React - [gin-admin](https://github.com/LyricTian/gin-admin)
2 |
3 | 基于 Ant Design Pro v4 实现的RBAC权限管理脚手架,目的是提供一套轻量的中后台开发框架,方便、快速的完成业务需求的开发。
4 |
5 |
6 | - [在线演示地址](http://demo.tiannianshou.com) (用户名:root,密码:abc-123)
7 | (`温馨提醒:为了达到更好的演示效果,这里给出了拥有最高权限的用户,请手下留情,只操作自己新增的数据,不要动平台本身的数据!谢谢!`)
8 |
9 | ## 获取并运行
10 | ```
11 | git clone https://github.com/gin-admin/gin-admin-react.git
12 | cd gin-admin-react
13 | yarn
14 | yarn start
15 | ```
16 |
17 | ## 打包编译
18 | ```
19 | umi build
20 | ```
21 |
22 | ## 讨论组
23 | > QQ群: 1409099
24 | >
25 |
26 | ## MIT License
27 | Copyright (c) 2019 gin-admin
28 |
--------------------------------------------------------------------------------
/config/config.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from './defaultSettings'; // https://umijs.org/config/
2 |
3 | import slash from 'slash2';
4 | import themePluginConfig from './themePluginConfig';
5 | import pageRoutes from './route.config';
6 | const { pwa } = defaultSettings; // preview.pro.ant.design only do not use in your production ;
7 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
8 |
9 | const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env;
10 | const isAntDesignProPreview = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site';
11 | const plugins = [
12 | [
13 | 'umi-plugin-react',
14 | {
15 | antd: true,
16 | dva: {
17 | hmr: true,
18 | },
19 | locale: {
20 | // default false
21 | enable: true,
22 | // default zh-CN
23 | default: 'zh-CN',
24 | // default true, when it is true, will use `navigator.language` overwrite default
25 | baseNavigator: true,
26 | },
27 | // dynamicImport: {
28 | // loadingComponent: './components/PageLoading/index',
29 | // webpackChunkName: true,
30 | // level: 3,
31 | // },
32 | pwa: pwa
33 | ? {
34 | workboxPluginMode: 'InjectManifest',
35 | workboxOptions: {
36 | importWorkboxFrom: 'local',
37 | },
38 | }
39 | : false, // default close dll, because issue https://github.com/ant-design/ant-design-pro/issues/4665
40 | // dll features https://webpack.js.org/plugins/dll-plugin/
41 | // dll: {
42 | // include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
43 | // exclude: ['@babel/runtime', 'netlify-lambda'],
44 | // },
45 | },
46 | ],
47 | [
48 | 'umi-plugin-pro-block',
49 | {
50 | moveMock: false,
51 | moveService: false,
52 | modifyRequest: true,
53 | autoAddMenu: true,
54 | },
55 | ],
56 | ];
57 |
58 | if (isAntDesignProPreview) {
59 | // 针对 preview.pro.ant.design 的 GA 统计代码
60 | plugins.push([
61 | 'umi-plugin-ga',
62 | {
63 | code: 'UA-72788897-6',
64 | },
65 | ]);
66 | plugins.push(['umi-plugin-antd-theme', themePluginConfig]);
67 | }
68 |
69 | export default {
70 | plugins,
71 | hash: true,
72 | targets: {
73 | ie: 11,
74 | },
75 | // umi routes: https://umijs.org/zh/guide/router.html
76 | routes: pageRoutes,
77 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn
78 | theme: {
79 | // ...darkTheme,
80 | },
81 | define: {
82 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION:
83 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION || '', // preview.pro.ant.design only do not use in your production ; preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
84 | },
85 | ignoreMomentLocale: true,
86 | lessLoaderOptions: {
87 | javascriptEnabled: true,
88 | },
89 | disableRedirectHoist: true,
90 | cssLoaderOptions: {
91 | modules: true,
92 | getLocalIdent: (context, _, localName) => {
93 | if (
94 | context.resourcePath.includes('node_modules') ||
95 | context.resourcePath.includes('ant.design.pro.less') ||
96 | context.resourcePath.includes('global.less')
97 | ) {
98 | return localName;
99 | }
100 |
101 | const match = context.resourcePath.match(/src(.*)/);
102 |
103 | if (match && match[1]) {
104 | const antdProPath = match[1].replace('.less', '');
105 | const arr = slash(antdProPath)
106 | .split('/')
107 | .map(a => a.replace(/([A-Z])/g, '-$1'))
108 | .map(a => a.toLowerCase());
109 | return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
110 | }
111 |
112 | return localName;
113 | },
114 | },
115 | manifest: {
116 | basePath: '/',
117 | }, // chainWebpack: webpackPlugin,
118 | proxy: {
119 | '/api/': {
120 | target: 'http://127.0.0.1:10088',
121 | changeOrigin: true,
122 | },
123 | },
124 | };
125 |
--------------------------------------------------------------------------------
/config/defaultSettings.js:
--------------------------------------------------------------------------------
1 | export default {
2 | navTheme: "light",
3 | primaryColor: "#1890FF",
4 | layout: "topmenu",
5 | contentWidth: "Fluid",
6 | fixedHeader: true,
7 | autoHideHeader: true,
8 | fixSiderbar: false,
9 | menu: {
10 | "locale": true
11 | },
12 | title: "权限管理脚手架",
13 | pwa: false,
14 | iconfontUrl: "",
15 | collapse: true,
16 | language: "zh-CN"
17 | };
18 |
--------------------------------------------------------------------------------
/config/plugin.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | function getModulePackageName(module) {
4 | if (!module.context) return null;
5 | const nodeModulesPath = path.join(__dirname, '../node_modules/');
6 |
7 | if (module.context.substring(0, nodeModulesPath.length) !== nodeModulesPath) {
8 | return null;
9 | }
10 |
11 | const moduleRelativePath = module.context.substring(nodeModulesPath.length);
12 | const [moduleDirName] = moduleRelativePath.split(path.sep);
13 | let packageName = moduleDirName; // handle tree shaking
14 |
15 | if (packageName && packageName.match('^_')) {
16 | // eslint-disable-next-line prefer-destructuring
17 | packageName = packageName.match(/^_(@?[^@]+)/)[1];
18 | }
19 |
20 | return packageName;
21 | }
22 |
23 | export const webpackPlugin = config => {
24 | // optimize chunks
25 | config.optimization // share the same chunks across different modules
26 | .runtimeChunk(false)
27 | .splitChunks({
28 | chunks: 'async',
29 | name: 'vendors',
30 | maxInitialRequests: Infinity,
31 | minSize: 0,
32 | cacheGroups: {
33 | vendors: {
34 | test: module => {
35 | const packageName = getModulePackageName(module) || '';
36 |
37 | if (packageName) {
38 | return [
39 | 'bizcharts',
40 | 'gg-editor',
41 | 'g6',
42 | '@antv',
43 | 'gg-editor-core',
44 | 'bizcharts-plugin-slider',
45 | ].includes(packageName);
46 | }
47 |
48 | return false;
49 | },
50 |
51 | name(module) {
52 | const packageName = getModulePackageName(module);
53 |
54 | if (packageName) {
55 | if (['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0) {
56 | return 'viz'; // visualization package
57 | }
58 | }
59 |
60 | return 'misc';
61 | },
62 | },
63 | },
64 | });
65 | };
66 |
--------------------------------------------------------------------------------
/config/route.config.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/login',
4 | component: '../layouts/UserLayout',
5 | routes: [
6 | {
7 | name: 'login',
8 | path: '/login',
9 | component: './user/login',
10 | },
11 | ],
12 | },
13 | {
14 | path: '/',
15 | component: '../layouts/SecurityLayout',
16 | routes: [
17 | {
18 | path: '/',
19 | component: '../layouts/BasicLayout',
20 | // authority: ['admin', 'user'],
21 | routes: [
22 | {
23 | path: '/',
24 | redirect: '/welcome',
25 | },
26 | {
27 | path: '/welcome',
28 | name: 'welcome',
29 | icon: 'smile',
30 | component: './Welcome',
31 | },
32 | {
33 | path: '/demo',
34 | name: 'demo',
35 | icon: 'database', // component: './demo',
36 | },
37 | {
38 | path: '/user',
39 | name: 'user',
40 | icon: 'user',
41 | component: './user/list',
42 | },
43 | {
44 | path: '/system',
45 | name: 'system',
46 | icon: 'setting',
47 | routes: [
48 | {
49 | path: '/system/menu',
50 | name: 'menu',
51 | icon: 'menu',
52 | component: './system/menu',
53 | },
54 | {
55 | path: '/system/role',
56 | name: 'role',
57 | icon: 'user',
58 | component: './system/role',
59 | },
60 | ],
61 | },
62 | {
63 | component: './404',
64 | },
65 | ],
66 | },
67 | {
68 | component: './404',
69 | },
70 | ],
71 | },
72 | {
73 | component: './404',
74 | },
75 | ];
76 |
--------------------------------------------------------------------------------
/config/themePluginConfig.js:
--------------------------------------------------------------------------------
1 | export default {
2 | theme: [
3 | {
4 | key: 'dark',
5 | fileName: 'dark.css',
6 | theme: 'dark',
7 | },
8 | {
9 | key: 'dust',
10 | fileName: 'dust.css',
11 | modifyVars: {
12 | '@primary-color': '#F5222D',
13 | },
14 | },
15 | {
16 | key: 'volcano',
17 | fileName: 'volcano.css',
18 | modifyVars: {
19 | '@primary-color': '#FA541C',
20 | },
21 | },
22 | {
23 | key: 'sunset',
24 | fileName: 'sunset.css',
25 | modifyVars: {
26 | '@primary-color': '#FAAD14',
27 | },
28 | },
29 | {
30 | key: 'cyan',
31 | fileName: 'cyan.css',
32 | modifyVars: {
33 | '@primary-color': '#13C2C2',
34 | },
35 | },
36 | {
37 | key: 'green',
38 | fileName: 'green.css',
39 | modifyVars: {
40 | '@primary-color': '#52C41A',
41 | },
42 | },
43 | {
44 | key: 'geekblue',
45 | fileName: 'geekblue.css',
46 | modifyVars: {
47 | '@primary-color': '#2F54EB',
48 | },
49 | },
50 | {
51 | key: 'purple',
52 | fileName: 'purple.css',
53 | modifyVars: {
54 | '@primary-color': '#722ED1',
55 | },
56 | },
57 | {
58 | key: 'dust',
59 | theme: 'dark',
60 | fileName: 'dark-dust.css',
61 | modifyVars: {
62 | '@primary-color': '#F5222D',
63 | },
64 | },
65 | {
66 | key: 'volcano',
67 | theme: 'dark',
68 | fileName: 'dark-volcano.css',
69 | modifyVars: {
70 | '@primary-color': '#FA541C',
71 | },
72 | },
73 | {
74 | key: 'sunset',
75 | theme: 'dark',
76 | fileName: 'dark-sunset.css',
77 | modifyVars: {
78 | '@primary-color': '#FAAD14',
79 | },
80 | },
81 | {
82 | key: 'cyan',
83 | theme: 'dark',
84 | fileName: 'dark-cyan.css',
85 | modifyVars: {
86 | '@primary-color': '#13C2C2',
87 | },
88 | },
89 | {
90 | key: 'green',
91 | theme: 'dark',
92 | fileName: 'dark-green.css',
93 | modifyVars: {
94 | '@primary-color': '#52C41A',
95 | },
96 | },
97 | {
98 | key: 'geekblue',
99 | theme: 'dark',
100 | fileName: 'dark-geekblue.css',
101 | modifyVars: {
102 | '@primary-color': '#2F54EB',
103 | },
104 | },
105 | {
106 | key: 'purple',
107 | theme: 'dark',
108 | fileName: 'dark-purple.css',
109 | modifyVars: {
110 | '@primary-color': '#722ED1',
111 | },
112 | },
113 | ],
114 | };
115 |
--------------------------------------------------------------------------------
/jest-puppeteer.config.js:
--------------------------------------------------------------------------------
1 | // ps https://github.com/GoogleChrome/puppeteer/issues/3120
2 | module.exports = {
3 | launch: {
4 | args: [
5 | '--disable-gpu',
6 | '--disable-dev-shm-usage',
7 | '--no-first-run',
8 | '--no-zygote',
9 | '--no-sandbox',
10 | ],
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testURL: 'http://localhost:8000',
3 | preset: 'jest-puppeteer',
4 | globals: {
5 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
6 | localStorage: null,
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "emitDecoratorMetadata": true,
4 | "experimentalDecorators": true,
5 | "baseUrl": ".",
6 | "paths": {
7 | "@/*": ["./src/*"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ant-design-pro",
3 | "version": "1.0.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 site && npm run gh-pages",
10 | "fetch:blocks": "pro fetch-blocks && npm run prettier",
11 | "format-imports": "cross-env import-sort --write '**/*.{js,jsx,ts,tsx}'",
12 | "gh-pages": "cp CNAME ./dist/ && gh-pages -d dist",
13 | "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
14 | "lint": "npm run lint:js && npm run lint:style && npm run lint:prettier",
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": "check-prettier lint",
20 | "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
21 | "prettier": "prettier -c --write \"**/*\"",
22 | "start": "umi dev",
23 | "start:no-mock": "cross-env MOCK=none umi dev",
24 | "start:no-ui": "cross-env UMI_UI=none umi dev",
25 | "test": "umi test",
26 | "test:all": "node ./tests/run-tests.js",
27 | "test:component": "umi test ./src/components",
28 | "ui": "umi ui"
29 | },
30 | "husky": {
31 | "hooks": {
32 | "pre-commit": "npm run lint-staged"
33 | }
34 | },
35 | "lint-staged": {
36 | "**/*.less": "stylelint --syntax less",
37 | "**/*.{js,jsx,tsx,ts,less,md,json}": [
38 | "prettier --write",
39 | "git add"
40 | ],
41 | "**/*.{js,jsx}": "npm run lint-staged:js",
42 | "**/*.{js,ts,tsx}": "npm run lint-staged:js"
43 | },
44 | "browserslist": [
45 | "> 1%",
46 | "last 2 versions",
47 | "not ie <= 10"
48 | ],
49 | "dependencies": {
50 | "@ant-design/pro-layout": "^4.8.3",
51 | "@antv/data-set": "^0.10.2",
52 | "antd": "^3.23.6",
53 | "classnames": "^2.2.6",
54 | "dva": "^2.4.1",
55 | "lodash": "^4.17.11",
56 | "md5": "^2.2.1",
57 | "moment": "^2.24.0",
58 | "omit.js": "^1.0.2",
59 | "path-to-regexp": "2.4.0",
60 | "qs": "^6.9.0",
61 | "react": "^16.8.6",
62 | "react-copy-to-clipboard": "^5.0.1",
63 | "react-dom": "^16.8.6",
64 | "react-helmet": "^5.2.1",
65 | "redux": "^4.0.1",
66 | "umi": "^2.8.7",
67 | "umi-plugin-antd-theme": "^1.0.1",
68 | "umi-plugin-pro-block": "^1.3.2",
69 | "umi-plugin-react": "^1.9.5",
70 | "umi-request": "^1.0.8",
71 | "uuid": "^3.3.3"
72 | },
73 | "devDependencies": {
74 | "@ant-design/pro-cli": "^1.0.14",
75 | "@types/classnames": "^2.2.7",
76 | "@types/express": "^4.17.0",
77 | "@types/history": "^4.7.2",
78 | "@types/jest": "^24.0.13",
79 | "@types/lodash": "^4.14.144",
80 | "@types/qs": "^6.5.3",
81 | "@types/react": "^16.8.19",
82 | "@types/react-dom": "^16.8.4",
83 | "@types/react-helmet": "^5.0.13",
84 | "@umijs/fabric": "^2.0.0-beta.3",
85 | "chalk": "^3.0.0",
86 | "check-prettier": "^1.0.3",
87 | "cross-env": "^6.0.0",
88 | "cross-port-killer": "^1.1.1",
89 | "enzyme": "^3.9.0",
90 | "express": "^4.17.1",
91 | "gh-pages": "^2.0.1",
92 | "husky": "^3.0.0",
93 | "import-sort-cli": "^6.0.0",
94 | "import-sort-parser-babylon": "^6.0.0",
95 | "import-sort-parser-typescript": "^6.0.0",
96 | "import-sort-style-module": "^6.0.0",
97 | "jest-puppeteer": "^4.2.0",
98 | "lint-staged": "^9.0.0",
99 | "mockjs": "^1.0.1-beta3",
100 | "node-fetch": "^2.6.0",
101 | "prettier": "^1.19.1",
102 | "pro-download": "1.0.1",
103 | "stylelint": "^12.0.0",
104 | "umi-plugin-ga": "^1.1.3",
105 | "umi-plugin-pro": "^1.0.2",
106 | "umi-types": "^0.5.0"
107 | },
108 | "optionalDependencies": {
109 | "puppeteer": "^1.17.0"
110 | },
111 | "engines": {
112 | "node": ">=10.0.0"
113 | },
114 | "checkFiles": [
115 | "src/**/*.js*",
116 | "src/**/*.ts*",
117 | "src/**/*.less",
118 | "config/**/*.js*",
119 | "scripts/**/*.js"
120 | ]
121 | }
122 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gin-admin/gin-admin-react-pro4/d78023bb82df05a9763a11a98c3bd4f93cc45512/public/favicon.png
--------------------------------------------------------------------------------
/public/icons/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gin-admin/gin-admin-react-pro4/d78023bb82df05a9763a11a98c3bd4f93cc45512/public/icons/icon-128x128.png
--------------------------------------------------------------------------------
/public/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gin-admin/gin-admin-react-pro4/d78023bb82df05a9763a11a98c3bd4f93cc45512/public/icons/icon-192x192.png
--------------------------------------------------------------------------------
/public/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gin-admin/gin-admin-react-pro4/d78023bb82df05a9763a11a98c3bd4f93cc45512/public/icons/icon-512x512.png
--------------------------------------------------------------------------------
/screenshot_qq.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gin-admin/gin-admin-react-pro4/d78023bb82df05a9763a11a98c3bd4f93cc45512/screenshot_qq.jpeg
--------------------------------------------------------------------------------
/src/api.jsx:
--------------------------------------------------------------------------------
1 | const Domain = ''; /* 使用当前域名留空 */
2 | const BaseUrl = `${Domain}/api`;
3 |
4 | export const Public = {
5 | Login: {
6 | Base: `${BaseUrl}/v1/pub/login`,
7 | Exit: `${BaseUrl}/v1/pub/login/exit`,
8 | GetCaptcha: `${BaseUrl}/v1/pub/login/captchaid`,
9 | ResCaptcha: `${BaseUrl}/v1/pub/login/captcha`,
10 | },
11 | RefreshToken: `${BaseUrl}/v1/pub/refresh-token`,
12 | Current: {
13 | UpdatePassword: `${BaseUrl}/v1/pub/current/password`,
14 | GetUserInfo: `${BaseUrl}/v1/pub/current/user`,
15 | QueryUserMenuTree: `${BaseUrl}/v1/pub/current/menutree`,
16 | }
17 | };
18 |
19 | export const Demo = {
20 | Base: `${BaseUrl}/v1/demos`,
21 | };
22 |
23 | export const Menu = {
24 | Base: `${BaseUrl}/v1/menus`,
25 | Tree: `${BaseUrl}/v1/menus.tree`,
26 | };
27 |
28 | export const Role = {
29 | Base: `${BaseUrl}/v1/roles`,
30 | Select: `${BaseUrl}/v1/roles.select`,
31 | };
32 |
33 | export const User = {
34 | Base: `${BaseUrl}/v1/users`,
35 | };
36 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Authorized/Authorized.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Result } from 'antd';
3 | import check from './CheckPermissions';
4 |
5 | const Authorized = ({
6 | children,
7 | authority,
8 | noMatch = (
9 |
14 | ),
15 | }) => {
16 | const childrenRender = typeof children === 'undefined' ? null : children;
17 | const dom = check(authority, childrenRender, noMatch);
18 | return <>{dom}>;
19 | };
20 |
21 | export default Authorized;
22 |
--------------------------------------------------------------------------------
/src/components/Authorized/AuthorizedRoute.jsx:
--------------------------------------------------------------------------------
1 | import { Redirect, Route } from 'umi';
2 | import React from 'react';
3 | import Authorized from './Authorized';
4 |
5 | const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
6 | (
12 |
17 | )}
18 | />
19 | }
20 | >
21 | (Component ? : render(props))} />
22 |
23 | );
24 |
25 | export default AuthorizedRoute;
26 |
--------------------------------------------------------------------------------
/src/components/Authorized/CheckPermissions.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CURRENT } from './renderAuthorize'; // eslint-disable-next-line import/no-cycle
3 |
4 | import PromiseRender from './PromiseRender';
5 |
6 | /**
7 | * 通用权限检查方法
8 | * Common check permissions method
9 | * @param { 权限判定 | Permission judgment } authority
10 | * @param { 你的权限 | Your permission description } currentAuthority
11 | * @param { 通过的组件 | Passing components } target
12 | * @param { 未通过的组件 | no pass components } Exception
13 | */
14 | const checkPermissions = (authority, currentAuthority, target, Exception) => {
15 | // 没有判定权限.默认查看所有
16 | // Retirement authority, return target;
17 | if (!authority) {
18 | return target;
19 | } // 数组处理
20 |
21 | if (Array.isArray(authority)) {
22 | if (Array.isArray(currentAuthority)) {
23 | if (currentAuthority.some(item => authority.includes(item))) {
24 | return target;
25 | }
26 | } else if (authority.includes(currentAuthority)) {
27 | return target;
28 | }
29 |
30 | return Exception;
31 | } // string 处理
32 |
33 | if (typeof authority === 'string') {
34 | if (Array.isArray(currentAuthority)) {
35 | if (currentAuthority.some(item => authority === item)) {
36 | return target;
37 | }
38 | } else if (authority === currentAuthority) {
39 | return target;
40 | }
41 |
42 | return Exception;
43 | } // Promise 处理
44 |
45 | if (authority instanceof Promise) {
46 | return ;
47 | } // Function 处理
48 |
49 | if (typeof authority === 'function') {
50 | try {
51 | const bool = authority(currentAuthority); // 函数执行后返回值是 Promise
52 |
53 | if (bool instanceof Promise) {
54 | return ;
55 | }
56 |
57 | if (bool) {
58 | return target;
59 | }
60 |
61 | return Exception;
62 | } catch (error) {
63 | throw error;
64 | }
65 | }
66 |
67 | throw new Error('unsupported parameters');
68 | };
69 |
70 | export { checkPermissions };
71 |
72 | function check(authority, target, Exception) {
73 | return checkPermissions(authority, CURRENT, target, Exception);
74 | }
75 |
76 | export default check;
77 |
--------------------------------------------------------------------------------
/src/components/Authorized/PromiseRender.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Spin } from 'antd';
3 | import isEqual from 'lodash/isEqual';
4 | import { isComponentClass } from './Secured'; // eslint-disable-next-line import/no-cycle
5 |
6 | export default class PromiseRender extends React.Component {
7 | state = {
8 | component: () => null,
9 | };
10 |
11 | componentDidMount() {
12 | this.setRenderComponent(this.props);
13 | }
14 |
15 | shouldComponentUpdate = (nextProps, nextState) => {
16 | const { component } = this.state;
17 |
18 | if (!isEqual(nextProps, this.props)) {
19 | this.setRenderComponent(nextProps);
20 | }
21 |
22 | if (nextState.component !== component) return true;
23 | return false;
24 | }; // set render Component : ok or error
25 |
26 | setRenderComponent(props) {
27 | const ok = this.checkIsInstantiation(props.ok);
28 | const error = this.checkIsInstantiation(props.error);
29 | props.promise
30 | .then(() => {
31 | this.setState({
32 | component: ok,
33 | });
34 | return true;
35 | })
36 | .catch(() => {
37 | this.setState({
38 | component: error,
39 | });
40 | });
41 | } // Determine whether the incoming component has been instantiated
42 | // AuthorizedRoute is already instantiated
43 | // Authorized render is already instantiated, children is no instantiated
44 | // Secured is not instantiated
45 |
46 | checkIsInstantiation = target => {
47 | if (isComponentClass(target)) {
48 | const Target = target;
49 | return props => ;
50 | }
51 |
52 | if (React.isValidElement(target)) {
53 | return props => React.cloneElement(target, props);
54 | }
55 |
56 | return () => target;
57 | };
58 |
59 | render() {
60 | const { component: Component } = this.state;
61 | const { ok, error, promise, ...rest } = this.props;
62 | return Component ? (
63 |
64 | ) : (
65 |
74 |
75 |
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/Authorized/Secured.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CheckPermissions from './CheckPermissions';
3 | /**
4 | * 默认不能访问任何页面
5 | * default is "NULL"
6 | */
7 |
8 | const Exception403 = () => 403;
9 |
10 | export const isComponentClass = component => {
11 | if (!component) return false;
12 | const proto = Object.getPrototypeOf(component);
13 | if (proto === React.Component || proto === Function.prototype) return true;
14 | return isComponentClass(proto);
15 | }; // Determine whether the incoming component has been instantiated
16 | // AuthorizedRoute is already instantiated
17 | // Authorized render is already instantiated, children is no instantiated
18 | // Secured is not instantiated
19 |
20 | const checkIsInstantiation = target => {
21 | if (isComponentClass(target)) {
22 | const Target = target;
23 | return props => ;
24 | }
25 |
26 | if (React.isValidElement(target)) {
27 | return props => React.cloneElement(target, props);
28 | }
29 |
30 | return () => target;
31 | };
32 | /**
33 | * 用于判断是否拥有权限访问此 view 权限
34 | * authority 支持传入 string, () => boolean | Promise
35 | * e.g. 'user' 只有 user 用户能访问
36 | * e.g. 'user,admin' user 和 admin 都能访问
37 | * e.g. ()=>boolean 返回true能访问,返回false不能访问
38 | * e.g. Promise then 能访问 catch不能访问
39 | * e.g. authority support incoming string, () => boolean | Promise
40 | * e.g. 'user' only user user can access
41 | * e.g. 'user, admin' user and admin can access
42 | * e.g. () => boolean true to be able to visit, return false can not be accessed
43 | * e.g. Promise then can not access the visit to catch
44 | * @param {string | function | Promise} authority
45 | * @param {ReactNode} error 非必需参数
46 | */
47 |
48 | const authorize = (authority, error) => {
49 | /**
50 | * conversion into a class
51 | * 防止传入字符串时找不到staticContext造成报错
52 | * String parameters can cause staticContext not found error
53 | */
54 | let classError = false;
55 |
56 | if (error) {
57 | classError = () => error;
58 | }
59 |
60 | if (!authority) {
61 | throw new Error('authority is required');
62 | }
63 |
64 | return function decideAuthority(target) {
65 | const component = CheckPermissions(authority, target, classError || Exception403);
66 | return checkIsInstantiation(component);
67 | };
68 | };
69 |
70 | export default authorize;
71 |
--------------------------------------------------------------------------------
/src/components/Authorized/index.jsx:
--------------------------------------------------------------------------------
1 | import Authorized from './Authorized';
2 | import Secured from './Secured';
3 | import check from './CheckPermissions';
4 | import renderAuthorize from './renderAuthorize';
5 | Authorized.Secured = Secured;
6 | Authorized.check = check;
7 | const RenderAuthorize = renderAuthorize(Authorized);
8 | export default RenderAuthorize;
9 |
--------------------------------------------------------------------------------
/src/components/Authorized/renderAuthorize.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable eslint-comments/disable-enable-pair */
2 |
3 | /* eslint-disable import/no-mutable-exports */
4 | let CURRENT = 'NULL';
5 |
6 | /**
7 | * use authority or getAuthority
8 | * @param {string|()=>String} currentAuthority
9 | */
10 | const renderAuthorize = Authorized => currentAuthority => {
11 | if (currentAuthority) {
12 | if (typeof currentAuthority === 'function') {
13 | CURRENT = currentAuthority();
14 | }
15 |
16 | if (
17 | Object.prototype.toString.call(currentAuthority) === '[object String]' ||
18 | Array.isArray(currentAuthority)
19 | ) {
20 | CURRENT = currentAuthority;
21 | }
22 | } else {
23 | CURRENT = 'NULL';
24 | }
25 |
26 | return Authorized;
27 | };
28 |
29 | export { CURRENT };
30 | export default Authorized => renderAuthorize(Authorized);
31 |
--------------------------------------------------------------------------------
/src/components/GlobalHeader/AvatarDropdown.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Avatar, Icon, Menu, Spin } from 'antd';
3 | import { FormattedMessage } from 'umi-plugin-react/locale';
4 | import { connect } from 'dva';
5 | import router from 'umi/router';
6 | import HeaderDropdown from '../HeaderDropdown';
7 | import styles from './index.less';
8 | import UpdatePasswordDialog from '@/pages/user/components/UpdatePasswordDialog';
9 |
10 | class AvatarDropdown extends React.Component {
11 | state = {
12 | updatePasswordVisible: false,
13 | };
14 |
15 | onMenuClick = event => {
16 | const { key } = event;
17 |
18 | if (key === 'logout') {
19 | const { dispatch } = this.props;
20 | dispatch({
21 | type: 'login/logout',
22 | });
23 | return;
24 | } else if (key === 'password') {
25 | this.setState({ updatePasswordVisible: true });
26 | return;
27 | }
28 |
29 | router.push(`/account/${key}`);
30 | };
31 |
32 | handleCancel = () => {
33 | this.setState({ updatePasswordVisible: false });
34 | };
35 |
36 | render() {
37 | const {
38 | currentUser = {
39 | avatar: '',
40 | name: '',
41 | },
42 | menu,
43 | } = this.props;
44 | const { updatePasswordVisible } = this.state;
45 | const menuHeaderDropdown = (
46 |
70 | );
71 | return currentUser && currentUser.real_name ? (
72 |
73 |
74 |
75 |
78 | {currentUser.real_name}
79 |
80 |
81 |
83 |
84 | ) : (
85 |
92 | );
93 | }
94 | }
95 |
96 | export default connect(({ global }) => ({
97 | currentUser: global.currentUser,
98 | }))(AvatarDropdown);
99 |
--------------------------------------------------------------------------------
/src/components/GlobalHeader/RightContent.jsx:
--------------------------------------------------------------------------------
1 | import { Icon, Tooltip } from 'antd';
2 | import React from 'react';
3 | import { connect } from 'dva';
4 | import { formatMessage } from 'umi-plugin-react/locale';
5 | import Avatar from './AvatarDropdown';
6 | import HeaderSearch from '../HeaderSearch';
7 | import SelectLang from '../SelectLang';
8 | import styles from './index.less';
9 |
10 | const GlobalHeaderRight = props => {
11 | const { theme, layout } = props;
12 | let className = styles.right;
13 |
14 | if (theme === 'dark' && layout === 'topmenu') {
15 | className = `${styles.right} ${styles.dark}`;
16 | }
17 |
18 | return (
19 |
20 |
{
38 | console.log('input', value);
39 | }}
40 | onPressEnter={value => {
41 | console.log('enter', value);
42 | }}
43 | />
44 |
49 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default connect(({ settings }) => ({
65 | theme: settings.navTheme,
66 | layout: settings.layout,
67 | }))(GlobalHeaderRight);
68 |
--------------------------------------------------------------------------------
/src/components/GlobalHeader/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | @pro-header-hover-bg: rgba(0, 0, 0, 0.025);
4 |
5 | .menu {
6 | :global(.anticon) {
7 | margin-right: 8px;
8 | }
9 | :global(.ant-dropdown-menu-item) {
10 | min-width: 160px;
11 | }
12 | }
13 |
14 | .right {
15 | float: right;
16 | height: 100%;
17 | margin-left: auto;
18 | overflow: hidden;
19 | .action {
20 | display: inline-block;
21 | height: 100%;
22 | padding: 0 12px;
23 | cursor: pointer;
24 | transition: all 0.3s;
25 | > i {
26 | color: @text-color;
27 | vertical-align: middle;
28 | }
29 | &:hover {
30 | background: @pro-header-hover-bg;
31 | }
32 | &:global(.opened) {
33 | background: @pro-header-hover-bg;
34 | }
35 | }
36 | .search {
37 | padding: 0 12px;
38 | &:hover {
39 | background: transparent;
40 | }
41 | }
42 | .account {
43 | .avatar {
44 | margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
45 | margin-right: 8px;
46 | color: @primary-color;
47 | vertical-align: top;
48 | color: #fff;
49 | background-color: rgb(24, 144, 255);
50 | }
51 | }
52 | }
53 |
54 | .dark {
55 | height: @layout-header-height;
56 | .action {
57 | color: rgba(255, 255, 255, 0.85);
58 | > i {
59 | color: rgba(255, 255, 255, 0.85);
60 | }
61 | &:hover,
62 | &:global(.opened) {
63 | background: @primary-color;
64 | }
65 | }
66 | }
67 |
68 | :global(.ant-pro-global-header) {
69 | .dark {
70 | .action {
71 | color: @text-color;
72 | > i {
73 | color: @text-color;
74 | }
75 | &:hover {
76 | color: rgba(255, 255, 255, 0.85);
77 | > i {
78 | color: rgba(255, 255, 255, 0.85);
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
85 | @media only screen and (max-width: @screen-md) {
86 | :global(.ant-divider-vertical) {
87 | vertical-align: unset;
88 | }
89 | .name {
90 | display: none;
91 | }
92 | .right {
93 | position: absolute;
94 | top: 0;
95 | right: 12px;
96 | .account {
97 | .avatar {
98 | margin-right: 0;
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/components/HeaderDropdown/index.jsx:
--------------------------------------------------------------------------------
1 | import { Dropdown } from 'antd';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 | import styles from './index.less';
5 |
6 | const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (
7 |
8 | );
9 |
10 | export default HeaderDropdown;
11 |
--------------------------------------------------------------------------------
/src/components/HeaderDropdown/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .container > * {
4 | background-color: @popover-bg;
5 | border-radius: 4px;
6 | box-shadow: @shadow-1-down;
7 | }
8 |
9 | @media screen and (max-width: @screen-xs) {
10 | .container {
11 | width: 100% !important;
12 | }
13 | .container > * {
14 | border-radius: 0 !important;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/HeaderSearch/index.jsx:
--------------------------------------------------------------------------------
1 | import { AutoComplete, Icon, Input } from 'antd';
2 | import React, { Component } from 'react';
3 | import classNames from 'classnames';
4 | import debounce from 'lodash/debounce';
5 | import styles from './index.less';
6 | export default class HeaderSearch extends Component {
7 | static defaultProps = {
8 | defaultActiveFirstOption: false,
9 | onPressEnter: () => {},
10 | onSearch: () => {},
11 | onChange: () => {},
12 | className: '',
13 | placeholder: '',
14 | dataSource: [],
15 | defaultOpen: false,
16 | onVisibleChange: () => {},
17 | };
18 |
19 | static getDerivedStateFromProps(props) {
20 | if ('open' in props) {
21 | return {
22 | searchMode: props.open,
23 | };
24 | }
25 |
26 | return null;
27 | }
28 |
29 | inputRef = null;
30 |
31 | constructor(props) {
32 | super(props);
33 | this.state = {
34 | searchMode: props.defaultOpen,
35 | value: props.defaultValue,
36 | };
37 | this.debouncePressEnter = debounce(this.debouncePressEnter, 500, {
38 | leading: true,
39 | trailing: false,
40 | });
41 | }
42 |
43 | onKeyDown = e => {
44 | if (e.key === 'Enter') {
45 | this.debouncePressEnter();
46 | }
47 | };
48 | onChange = value => {
49 | if (typeof value === 'string') {
50 | const { onSearch, onChange } = this.props;
51 | this.setState({
52 | value,
53 | });
54 |
55 | if (onSearch) {
56 | onSearch(value);
57 | }
58 |
59 | if (onChange) {
60 | onChange(value);
61 | }
62 | }
63 | };
64 | enterSearchMode = () => {
65 | const { onVisibleChange } = this.props;
66 | onVisibleChange(true);
67 | this.setState(
68 | {
69 | searchMode: true,
70 | },
71 | () => {
72 | const { searchMode } = this.state;
73 |
74 | if (searchMode && this.inputRef) {
75 | this.inputRef.focus();
76 | }
77 | },
78 | );
79 | };
80 | leaveSearchMode = () => {
81 | this.setState({
82 | searchMode: false,
83 | });
84 | };
85 | debouncePressEnter = () => {
86 | const { onPressEnter } = this.props;
87 | const { value } = this.state;
88 | onPressEnter(value || '');
89 | };
90 |
91 | render() {
92 | const { className, defaultValue, placeholder, open, ...restProps } = this.props;
93 | const { searchMode, value } = this.state;
94 | delete restProps.defaultOpen; // for rc-select not affected
95 |
96 | const inputClass = classNames(styles.input, {
97 | [styles.show]: searchMode,
98 | });
99 | return (
100 | {
104 | if (propertyName === 'width' && !searchMode) {
105 | const { onVisibleChange } = this.props;
106 | onVisibleChange(searchMode);
107 | }
108 | }}
109 | >
110 |
111 |
118 | {
120 | this.inputRef = node;
121 | }}
122 | defaultValue={defaultValue}
123 | aria-label={placeholder}
124 | placeholder={placeholder}
125 | onKeyDown={this.onKeyDown}
126 | onBlur={this.leaveSearchMode}
127 | />
128 |
129 |
130 | );
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/components/HeaderSearch/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .headerSearch {
4 | :global(.anticon-search) {
5 | font-size: 16px;
6 | cursor: pointer;
7 | }
8 | .input {
9 | width: 0;
10 | background: transparent;
11 | border-radius: 0;
12 | transition: width 0.3s, margin-left 0.3s;
13 | :global(.ant-select-selection) {
14 | background: transparent;
15 | }
16 | input {
17 | padding-right: 0;
18 | padding-left: 0;
19 | border: 0;
20 | box-shadow: none !important;
21 | }
22 | &,
23 | &:hover,
24 | &:focus {
25 | border-bottom: 1px solid @border-color-base;
26 | }
27 | &.show {
28 | width: 210px;
29 | margin-left: 8px;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/NoticeIcon/NoticeList.jsx:
--------------------------------------------------------------------------------
1 | import { Avatar, List } from 'antd';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 | import styles from './NoticeList.less';
5 |
6 | const NoticeList = ({
7 | data = [],
8 | onClick,
9 | onClear,
10 | title,
11 | onViewMore,
12 | emptyText,
13 | showClear = true,
14 | clearText,
15 | viewMoreText,
16 | showViewMore = false,
17 | }) => {
18 | if (data.length === 0) {
19 | return (
20 |
21 |

25 |
{emptyText}
26 |
27 | );
28 | }
29 |
30 | return (
31 |
32 |
{
36 | const itemCls = classNames(styles.item, {
37 | [styles.read]: item.read,
38 | }); // eslint-disable-next-line no-nested-ternary
39 |
40 | const leftIcon = item.avatar ? (
41 | typeof item.avatar === 'string' ? (
42 |
43 | ) : (
44 | {item.avatar}
45 | )
46 | ) : null;
47 | return (
48 | onClick && onClick(item)}
52 | >
53 |
58 | {item.title}
59 | {item.extra}
60 |
61 | }
62 | description={
63 |
64 |
{item.description}
65 |
{item.datetime}
66 |
67 | }
68 | />
69 |
70 | );
71 | }}
72 | />
73 |
74 | {showClear ? (
75 |
76 | {clearText} {title}
77 |
78 | ) : null}
79 | {showViewMore ? (
80 |
{
82 | if (onViewMore) {
83 | onViewMore(e);
84 | }
85 | }}
86 | >
87 | {viewMoreText}
88 |
89 | ) : null}
90 |
91 |
92 | );
93 | };
94 |
95 | export default NoticeList;
96 |
--------------------------------------------------------------------------------
/src/components/NoticeIcon/NoticeList.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .list {
4 | max-height: 400px;
5 | overflow: auto;
6 | &::-webkit-scrollbar {
7 | display: none;
8 | }
9 | .item {
10 | padding-right: 24px;
11 | padding-left: 24px;
12 | overflow: hidden;
13 | cursor: pointer;
14 | transition: all 0.3s;
15 |
16 | .meta {
17 | width: 100%;
18 | }
19 |
20 | .avatar {
21 | margin-top: 4px;
22 | background: #fff;
23 | }
24 | .iconElement {
25 | font-size: 32px;
26 | }
27 |
28 | &.read {
29 | opacity: 0.4;
30 | }
31 | &:last-child {
32 | border-bottom: 0;
33 | }
34 | &:hover {
35 | background: @primary-1;
36 | }
37 | .title {
38 | margin-bottom: 8px;
39 | font-weight: normal;
40 | }
41 | .description {
42 | font-size: 12px;
43 | line-height: @line-height-base;
44 | }
45 | .datetime {
46 | margin-top: 4px;
47 | font-size: 12px;
48 | line-height: @line-height-base;
49 | }
50 | .extra {
51 | float: right;
52 | margin-top: -1.5px;
53 | margin-right: 0;
54 | color: @text-color-secondary;
55 | font-weight: normal;
56 | }
57 | }
58 | .loadMore {
59 | padding: 8px 0;
60 | color: @primary-6;
61 | text-align: center;
62 | cursor: pointer;
63 | &.loadedAll {
64 | color: rgba(0, 0, 0, 0.25);
65 | cursor: unset;
66 | }
67 | }
68 | }
69 |
70 | .notFound {
71 | padding: 73px 0 88px;
72 | color: @text-color-secondary;
73 | text-align: center;
74 | img {
75 | display: inline-block;
76 | height: 76px;
77 | margin-bottom: 16px;
78 | }
79 | }
80 |
81 | .bottomBar {
82 | height: 46px;
83 | color: @text-color;
84 | line-height: 46px;
85 | text-align: center;
86 | border-top: 1px solid @border-color-split;
87 | border-radius: 0 0 @border-radius-base @border-radius-base;
88 | transition: all 0.3s;
89 | div {
90 | display: inline-block;
91 | width: 50%;
92 | cursor: pointer;
93 | transition: all 0.3s;
94 | user-select: none;
95 |
96 | &:only-child {
97 | width: 100%;
98 | }
99 | &:not(:only-child):last-child {
100 | border-left: 1px solid @border-color-split;
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/components/NoticeIcon/index.jsx:
--------------------------------------------------------------------------------
1 | import { Badge, Icon, Spin, Tabs } from 'antd';
2 | import React, { Component } from 'react';
3 | import classNames from 'classnames';
4 | import NoticeList from './NoticeList';
5 | import HeaderDropdown from '../HeaderDropdown';
6 | import styles from './index.less';
7 | const { TabPane } = Tabs;
8 | export default class NoticeIcon extends Component {
9 | static Tab = NoticeList;
10 | static defaultProps = {
11 | onItemClick: () => {},
12 | onPopupVisibleChange: () => {},
13 | onTabChange: () => {},
14 | onClear: () => {},
15 | onViewMore: () => {},
16 | loading: false,
17 | clearClose: false,
18 | emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
19 | };
20 | state = {
21 | visible: false,
22 | };
23 | onItemClick = (item, tabProps) => {
24 | const { onItemClick } = this.props;
25 |
26 | if (onItemClick) {
27 | onItemClick(item, tabProps);
28 | }
29 | };
30 | onClear = (name, key) => {
31 | const { onClear } = this.props;
32 |
33 | if (onClear) {
34 | onClear(name, key);
35 | }
36 | };
37 | onTabChange = tabType => {
38 | const { onTabChange } = this.props;
39 |
40 | if (onTabChange) {
41 | onTabChange(tabType);
42 | }
43 | };
44 | onViewMore = (tabProps, event) => {
45 | const { onViewMore } = this.props;
46 |
47 | if (onViewMore) {
48 | onViewMore(tabProps, event);
49 | }
50 | };
51 |
52 | getNotificationBox() {
53 | const { children, loading, clearText, viewMoreText } = this.props;
54 |
55 | if (!children) {
56 | return null;
57 | }
58 |
59 | const panes = React.Children.map(children, child => {
60 | if (!child) {
61 | return null;
62 | }
63 |
64 | const { list, title, count, tabKey, showClear, showViewMore } = child.props;
65 | const len = list && list.length ? list.length : 0;
66 | const msgCount = count || count === 0 ? count : len;
67 | const tabTitle = msgCount > 0 ? `${title} (${msgCount})` : title;
68 | return (
69 |
70 | this.onClear(title, tabKey)}
75 | onClick={item => this.onItemClick(item, child.props)}
76 | onViewMore={event => this.onViewMore(child.props, event)}
77 | showClear={showClear}
78 | showViewMore={showViewMore}
79 | title={title}
80 | {...child.props}
81 | />
82 |
83 | );
84 | });
85 | return (
86 | <>
87 |
88 |
89 | {panes}
90 |
91 |
92 | >
93 | );
94 | }
95 |
96 | handleVisibleChange = visible => {
97 | const { onPopupVisibleChange } = this.props;
98 | this.setState({
99 | visible,
100 | });
101 |
102 | if (onPopupVisibleChange) {
103 | onPopupVisibleChange(visible);
104 | }
105 | };
106 |
107 | render() {
108 | const { className, count, popupVisible, bell } = this.props;
109 | const { visible } = this.state;
110 | const noticeButtonClass = classNames(className, styles.noticeButton);
111 | const notificationBox = this.getNotificationBox();
112 | const NoticeBellIcon = bell || ;
113 | const trigger = (
114 |
119 |
126 | {NoticeBellIcon}
127 |
128 |
129 | );
130 |
131 | if (!notificationBox) {
132 | return trigger;
133 | }
134 |
135 | const popoverProps = {};
136 |
137 | if ('popupVisible' in this.props) {
138 | popoverProps.visible = popupVisible;
139 | }
140 |
141 | return (
142 |
151 | {trigger}
152 |
153 | );
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/components/NoticeIcon/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .popover {
4 | position: relative;
5 | width: 336px;
6 | }
7 |
8 | .noticeButton {
9 | display: inline-block;
10 | cursor: pointer;
11 | transition: all 0.3s;
12 | }
13 | .icon {
14 | padding: 4px;
15 | vertical-align: middle;
16 | }
17 |
18 | .badge {
19 | font-size: 16px;
20 | }
21 |
22 | .tabs {
23 | :global {
24 | .ant-tabs-nav-scroll {
25 | text-align: center;
26 | }
27 | .ant-tabs-bar {
28 | margin-bottom: 0;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/PageLoading/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Spin } from 'antd'; // loading components from code split
3 | // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
4 |
5 | const PageLoading = () => (
6 |
12 |
13 |
14 | );
15 |
16 | export default PageLoading;
17 |
--------------------------------------------------------------------------------
/src/components/SelectLang/index.jsx:
--------------------------------------------------------------------------------
1 | import { Icon, Menu } from 'antd';
2 | import { formatMessage, getLocale, setLocale } from 'umi-plugin-react/locale';
3 | import React from 'react';
4 | import classNames from 'classnames';
5 | import HeaderDropdown from '../HeaderDropdown';
6 | import styles from './index.less';
7 |
8 | const SelectLang = props => {
9 | const { className } = props;
10 | const selectedLang = getLocale();
11 |
12 | const changeLang = ({ key }) => setLocale(key);
13 |
14 | const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR'];
15 | const languageLabels = {
16 | 'zh-CN': '简体中文',
17 | 'zh-TW': '繁体中文',
18 | 'en-US': 'English',
19 | 'pt-BR': 'Português',
20 | };
21 | const languageIcons = {
22 | 'zh-CN': '🇨🇳',
23 | 'zh-TW': '🇭🇰',
24 | 'en-US': '🇺🇸',
25 | 'pt-BR': '🇧🇷',
26 | };
27 | const langMenu = (
28 |
38 | );
39 | return (
40 |
41 |
42 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default SelectLang;
54 |
--------------------------------------------------------------------------------
/src/components/SelectLang/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .menu {
4 | :global(.anticon) {
5 | margin-right: 8px;
6 | }
7 | :global(.ant-dropdown-menu-item) {
8 | min-width: 160px;
9 | }
10 | }
11 |
12 | .dropDown {
13 | line-height: @layout-header-height;
14 | vertical-align: top;
15 | cursor: pointer;
16 | > i {
17 | font-size: 16px !important;
18 | transform: none !important;
19 | svg {
20 | position: relative;
21 | top: -1px;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/e2e/__mocks__/antd-pro-merge-less.js:
--------------------------------------------------------------------------------
1 | export default undefined;
2 |
--------------------------------------------------------------------------------
/src/e2e/baseLayout.e2e.js:
--------------------------------------------------------------------------------
1 | const { uniq } = require('lodash');
2 | const RouterConfig = require('../../config/config').default.routes;
3 |
4 | const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
5 |
6 | function formatter(routes, parentPath = '') {
7 | const fixedParentPath = parentPath.replace(/\/{1,}/g, '/');
8 | let result = [];
9 | routes.forEach(item => {
10 | if (item.path) {
11 | result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/'));
12 | }
13 | if (item.routes) {
14 | result = result.concat(
15 | formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath),
16 | );
17 | }
18 | });
19 | return uniq(result.filter(item => !!item));
20 | }
21 |
22 | beforeAll(async () => {
23 | await page.goto(`${BASE_URL}`);
24 | await page.evaluate(() => {
25 | localStorage.setItem('antd-pro-authority', '["admin"]');
26 | });
27 | });
28 |
29 | describe('Ant Design Pro E2E test', () => {
30 | const testPage = path => async () => {
31 | await page.goto(`${BASE_URL}${path}`);
32 | await page.waitForSelector('footer', {
33 | timeout: 2000,
34 | });
35 | const haveFooter = await page.evaluate(
36 | () => document.getElementsByTagName('footer').length > 0,
37 | );
38 | expect(haveFooter).toBeTruthy();
39 | };
40 |
41 | const routers = formatter(RouterConfig);
42 | routers.forEach(route => {
43 | it(`test pages ${route}`, testPage(route));
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/src/e2e/topMenu.e2e.js:
--------------------------------------------------------------------------------
1 | const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
2 |
3 | describe('Homepage', () => {
4 | it('topmenu should have footer', async () => {
5 | const params = '?navTheme=light&layout=topmenu';
6 | await page.goto(`${BASE_URL}${params}`);
7 | await page.waitForSelector('footer', {
8 | timeout: 2000,
9 | });
10 | const haveFooter = await page.evaluate(
11 | () => document.getElementsByTagName('footer').length > 0,
12 | );
13 | expect(haveFooter).toBeTruthy();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/src/global.jsx:
--------------------------------------------------------------------------------
1 | import { Button, message, notification } from 'antd';
2 | import React from 'react';
3 | import { formatMessage } from 'umi-plugin-react/locale';
4 | import defaultSettings from '../config/defaultSettings';
5 | const { pwa } = defaultSettings; // if pwa is true
6 |
7 | if (pwa) {
8 | // Notify user if offline now
9 | window.addEventListener('sw.offline', () => {
10 | message.warning(
11 | formatMessage({
12 | id: 'app.pwa.offline',
13 | }),
14 | );
15 | }); // Pop up a prompt on the page asking the user if they want to use the latest version
16 |
17 | window.addEventListener('sw.updated', event => {
18 | const e = event;
19 |
20 | const reloadSW = async () => {
21 | // Check if there is sw whose state is waiting in ServiceWorkerRegistration
22 | // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
23 | const worker = e.detail && e.detail.waiting;
24 |
25 | if (!worker) {
26 | return true;
27 | } // Send skip-waiting event to waiting SW with MessageChannel
28 |
29 | await new Promise((resolve, reject) => {
30 | const channel = new MessageChannel();
31 |
32 | channel.port1.onmessage = msgEvent => {
33 | if (msgEvent.data.error) {
34 | reject(msgEvent.data.error);
35 | } else {
36 | resolve(msgEvent.data);
37 | }
38 | };
39 |
40 | worker.postMessage(
41 | {
42 | type: 'skip-waiting',
43 | },
44 | [channel.port2],
45 | );
46 | }); // Refresh current page to use the updated HTML and other assets after SW has skiped waiting
47 |
48 | window.location.reload(true);
49 | return true;
50 | };
51 |
52 | const key = `open${Date.now()}`;
53 | const btn = (
54 |
65 | );
66 | notification.open({
67 | message: formatMessage({
68 | id: 'app.pwa.serviceworker.updated',
69 | }),
70 | description: formatMessage({
71 | id: 'app.pwa.serviceworker.updated.hint',
72 | }),
73 | btn,
74 | key,
75 | onClose: async () => {},
76 | });
77 | });
78 | } else if ('serviceWorker' in navigator) {
79 | // unregister service worker
80 | const { serviceWorker } = navigator;
81 |
82 | if (serviceWorker.getRegistrations) {
83 | serviceWorker.getRegistrations().then(sws => {
84 | sws.forEach(sw => {
85 | sw.unregister();
86 | });
87 | });
88 | }
89 |
90 | serviceWorker.getRegistration().then(sw => {
91 | if (sw) sw.unregister();
92 | }); // remove all caches
93 |
94 | if (window.caches && window.caches.keys) {
95 | caches.keys().then(keys => {
96 | keys.forEach(key => {
97 | caches.delete(key);
98 | });
99 | });
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/global.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | html,
4 | body,
5 | #root {
6 | height: 100%;
7 | }
8 |
9 | .colorWeak {
10 | filter: invert(80%);
11 | }
12 |
13 | .ant-layout {
14 | min-height: 100vh;
15 | }
16 |
17 | canvas {
18 | display: block;
19 | }
20 |
21 | body {
22 | text-rendering: optimizeLegibility;
23 | -webkit-font-smoothing: antialiased;
24 | -moz-osx-font-smoothing: grayscale;
25 | }
26 |
27 | ul,
28 | ol {
29 | list-style: none;
30 | }
31 |
32 | @media (max-width: @screen-xs) {
33 | .ant-table {
34 | width: 100%;
35 | overflow-x: auto;
36 | &-thead > tr,
37 | &-tbody > tr {
38 | > th,
39 | > td {
40 | white-space: pre;
41 | > span {
42 | display: block;
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/layouts/BasicLayout.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
3 | * You can view component api by:
4 | * https://github.com/ant-design/ant-design-pro-layout
5 | */
6 | import ProLayout, { DefaultFooter } from '@ant-design/pro-layout';
7 | import React, { useEffect } from 'react';
8 | import Link from 'umi/link';
9 | import { connect } from 'dva';
10 | import { Icon, Result, Button } from 'antd';
11 | import { formatMessage } from 'umi-plugin-react/locale';
12 | import Authorized from '@/utils/Authorized';
13 | import RightContent from '@/components/GlobalHeader/RightContent';
14 | import { isAntDesignPro, getAuthorityFromRouter } from '@/utils/utils';
15 | import logo from '../assets/logo.svg';
16 | const noMatch = (
17 |
23 | Go Login
24 |
25 | }
26 | />
27 | );
28 |
29 | /**
30 | * use Authorized check all menu item
31 | */
32 | const menuDataRender = menuList =>
33 | menuList.map(item => {
34 | const localItem = { ...item, children: item.children ? menuDataRender(item.children) : [] };
35 | return Authorized.check(item.authority, localItem, null);
36 | });
37 |
38 | const defaultFooterDom = (
39 |
43 | );
44 |
45 | const footerRender = () => {
46 | if (!isAntDesignPro()) {
47 | return defaultFooterDom;
48 | }
49 |
50 | return (
51 | <>
52 | {defaultFooterDom}
53 |
67 | >
68 | );
69 | };
70 |
71 | const BasicLayout = props => {
72 | const {
73 | dispatch,
74 | children,
75 | settings,
76 | location = {
77 | pathname: '/',
78 | },
79 | } = props;
80 | /**
81 | * constructor
82 | */
83 |
84 | useEffect(() => {
85 | if (dispatch) {
86 | dispatch({
87 | type: 'global/fetchCurrent',
88 | });
89 | }
90 | }, []);
91 | /**
92 | * init variables
93 | */
94 |
95 | const handleMenuCollapse = payload => {
96 | if (dispatch) {
97 | dispatch({
98 | type: 'global/changeLayoutCollapsed',
99 | payload,
100 | });
101 | }
102 | }; // get children authority
103 |
104 | const { global: { routes } } = props;
105 | if (routes.length > 0) {
106 | props.route.routes = routes;
107 | }
108 |
109 | const authorized = getAuthorityFromRouter(props.route.routes, location.pathname || '/') || {
110 | authority: undefined,
111 | };
112 | return (
113 | (
116 |
117 | {logoDom}
118 | {titleDom}
119 |
120 | )}
121 | onCollapse={handleMenuCollapse}
122 | menuItemRender={(menuItemProps, defaultDom) => {
123 | if (menuItemProps.isUrl || menuItemProps.children) {
124 | return defaultDom;
125 | }
126 |
127 | return {defaultDom};
128 | }}
129 | breadcrumbRender={(routers = []) => [
130 | {
131 | path: '/',
132 | breadcrumbName: formatMessage({
133 | id: 'menu.home',
134 | defaultMessage: 'Home',
135 | }),
136 | },
137 | ...routers,
138 | ]}
139 | itemRender={(route, params, routes, paths) => {
140 | const first = routes.indexOf(route) === 0;
141 | return first ? (
142 | {route.breadcrumbName}
143 | ) : (
144 | {route.breadcrumbName}
145 | );
146 | }}
147 | footerRender={footerRender}
148 | menuDataRender={menuDataRender}
149 | formatMessage={formatMessage}
150 | rightContentRender={rightProps => }
151 | {...props}
152 | {...settings}
153 | >
154 |
155 | {children}
156 |
157 |
158 | );
159 | };
160 |
161 | export default connect(({ global, settings }) => ({
162 | collapsed: global.collapsed,
163 | global,
164 | settings,
165 | }))(BasicLayout);
166 |
--------------------------------------------------------------------------------
/src/layouts/BlankLayout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Layout = ({ children }) => {children}
;
4 |
5 | export default Layout;
6 |
--------------------------------------------------------------------------------
/src/layouts/SecurityLayout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'dva';
3 | import { Redirect } from 'umi';
4 | import { stringify } from 'querystring';
5 | import PageLoading from '@/components/PageLoading';
6 | import store from '@/utils/store';
7 |
8 | class SecurityLayout extends React.Component {
9 | state = {
10 | isReady: false,
11 | };
12 |
13 | componentDidMount() {
14 | this.setState({
15 | isReady: true,
16 | });
17 | const { dispatch } = this.props;
18 |
19 | dispatch({
20 | type: 'global/fetchCurrent',
21 | success: () => {
22 | dispatch({
23 | type: 'global/fetchMenuTree',
24 | payload: {
25 | routes : this.props.route.routes[0].routes,
26 | },
27 | });
28 | },
29 | });
30 | }
31 |
32 | render() {
33 | const { isReady } = this.state;
34 | const { children, loading } = this.props; // You can replace it to your authentication rule (such as check token exists)
35 | // 你可以把它替换成你自己的登录认证规则(比如判断 token 是否存在)
36 |
37 | const isLogin = store.getAccessToken();
38 | const queryString = stringify({
39 | redirect: window.location.href,
40 | });
41 |
42 | if ((!isLogin && loading) || !isReady) {
43 | return ;
44 | }
45 |
46 | if (!isLogin) {
47 | return ;
48 | }
49 |
50 | return children;
51 | }
52 | }
53 |
54 | export default connect(({ loading }) => ({
55 | loading: loading.models.global,
56 | }))(SecurityLayout);
57 |
--------------------------------------------------------------------------------
/src/layouts/UserLayout.jsx:
--------------------------------------------------------------------------------
1 | import { DefaultFooter, getMenuData, getPageTitle } from '@ant-design/pro-layout';
2 | import { Helmet } from 'react-helmet';
3 | import Link from 'umi/link';
4 | import React from 'react';
5 | import { connect } from 'dva';
6 | import { formatMessage } from 'umi-plugin-react/locale';
7 | import SelectLang from '@/components/SelectLang';
8 | import logo from '../assets/logo.svg';
9 | import styles from './UserLayout.less';
10 |
11 | const UserLayout = props => {
12 | const {
13 | route = {
14 | routes: [],
15 | },
16 | } = props;
17 | const { routes = [] } = route;
18 | const {
19 | children,
20 | location = {
21 | pathname: '',
22 | },
23 | } = props;
24 | const { breadcrumb } = getMenuData(routes);
25 | const title = getPageTitle({
26 | pathname: location.pathname,
27 | breadcrumb,
28 | formatMessage,
29 | ...props,
30 | });
31 | return (
32 | <>
33 |
34 | {title}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |

47 |
{formatMessage({
48 | id: 'app.name',
49 | })}
50 |
51 |
52 |
{formatMessage({
53 | id: 'app.desc',
54 | })}
55 |
56 | {children}
57 |
58 |
59 |
60 | >
61 | );
62 | };
63 |
64 | export default connect(({ settings }) => ({ ...settings }))(UserLayout);
65 |
--------------------------------------------------------------------------------
/src/layouts/UserLayout.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .container {
4 | display: flex;
5 | flex-direction: column;
6 | height: 100vh;
7 | overflow: auto;
8 | background: @layout-body-background;
9 | }
10 |
11 | .lang {
12 | width: 100%;
13 | height: 40px;
14 | line-height: 44px;
15 | text-align: right;
16 | :global(.ant-dropdown-trigger) {
17 | margin-right: 24px;
18 | }
19 | }
20 |
21 | .content {
22 | flex: 1;
23 | padding: 32px 0;
24 | }
25 |
26 | @media (min-width: @screen-md-min) {
27 | .container {
28 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
29 | background-repeat: no-repeat;
30 | background-position: center 110px;
31 | background-size: 100%;
32 | }
33 |
34 | .content {
35 | padding: 32px 0 24px;
36 | }
37 | }
38 |
39 | .top {
40 | text-align: center;
41 | }
42 |
43 | .header {
44 | height: 44px;
45 | line-height: 44px;
46 | a {
47 | text-decoration: none;
48 | }
49 | }
50 |
51 | .logo {
52 | height: 44px;
53 | margin-right: 16px;
54 | vertical-align: top;
55 | }
56 |
57 | .title {
58 | position: relative;
59 | top: 2px;
60 | color: @heading-color;
61 | font-weight: 600;
62 | font-size: 33px;
63 | font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
64 | }
65 |
66 | .desc {
67 | margin-top: 12px;
68 | margin-bottom: 40px;
69 | color: @text-color-secondary;
70 | font-size: @font-size-base;
71 | }
72 |
--------------------------------------------------------------------------------
/src/locales/en-US.js:
--------------------------------------------------------------------------------
1 | import component from './en-US/component';
2 | import globalHeader from './en-US/globalHeader';
3 | import menu from './en-US/menu';
4 | import user from './en-US/user';
5 | import role from './en-US/role';
6 | import pwa from './en-US/pwa';
7 | import settingDrawer from './en-US/settingDrawer';
8 | import settings from './en-US/settings';
9 | export default {
10 | 'navBar.lang': 'Languages',
11 | 'layout.user.link.help': 'Help',
12 | 'layout.user.link.privacy': 'Privacy',
13 | 'layout.user.link.terms': 'Terms',
14 | 'app.name': 'gin-admin-react',
15 | 'app.desc': 'gin-admin-react Based on Ant Design Pro v4',
16 | 'app.welcome.title': 'gin-admin v5.2.0 released, start the experience with gin-admin-cli.',
17 | 'app.welcome.link.gin-admin-react': 'In case of problems, please keep gin-admin up to date. Still have issues, please report it',
18 | 'app.welcome.project.gin-admin': 'Use gin-admin-cli to quickly build gin-admin',
19 | ...globalHeader,
20 | ...menu,
21 | ...user,
22 | ...role,
23 | ...settingDrawer,
24 | ...settings,
25 | ...pwa,
26 | ...component,
27 | };
28 |
--------------------------------------------------------------------------------
/src/locales/en-US/component.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.tagSelect.expand': 'Expand',
3 | 'component.tagSelect.collapse': 'Collapse',
4 | 'component.tagSelect.all': 'All',
5 | 'component.name:': 'Name:',
6 | 'component.name:.content': 'Name: {content}',
7 | 'component.button.confirm': 'Confirm',
8 | 'component.button.cancel': 'Cancel',
9 | 'component.template': 'Template',
10 | 'component.template.basic': 'Basic',
11 | 'component.template.advanced': 'Advanced',
12 | 'component.option.all': 'All',
13 | 'component.option.normal': 'Normal',
14 | 'component.option.ban': 'Ban',
15 | 'component.option.show': 'Show',
16 | 'component.option.hidden': 'Hidden',
17 | 'component.operation': 'Operation',
18 | 'component.operation.edit': 'Edit',
19 | 'component.operation.create': 'Create',
20 | 'component.operation.delete': 'Delete',
21 | 'component.operation.update': 'Edit',
22 | 'component.operation.query': 'Query',
23 | 'component.operation.enable': 'Enable',
24 | 'component.operation.disable': 'Disable',
25 | 'component.operation.create.content': 'Create {content}',
26 | 'component.operation.update.content': 'Edit {content}',
27 | 'component.operation.delete.content': 'Delete {content}',
28 | 'component.operation.enable.content': 'Enable {content}',
29 | 'component.operation.disable.content': 'Disable {content}',
30 | 'component.operation.delete.confirm': 'Confirm deletion?',
31 | 'component.operation.reset': 'Reset',
32 | 'component.searchList.search': 'Search',
33 | 'component.searchList.reset': 'Reset',
34 | 'component.searchList.status': 'Status',
35 | 'component.searchList.result.total': 'Total {total} items',
36 | 'component.placeholder': 'Please type',
37 | 'component.placeholder.select': 'Please select',
38 | 'component.placeholder.content': 'Please type {content}',
39 | 'component.placeholder.select.content': 'Please select {content}',
40 | 'component.notification.request.success': 'Successful',
41 | 'component.notification.request.fail': 'Failed',
42 | 'component.notification.request.success.content': '{content} successful',
43 | 'component.notification.request.fail.content': '{content} failed',
44 | 'component.icon.only.support.official': 'Only official Icon is supported',
45 | 'component.order.description.desc': 'Descending order',
46 | 'component.order.description.asc': 'Ascending order',
47 | };
48 |
--------------------------------------------------------------------------------
/src/locales/en-US/globalHeader.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.globalHeader.search': 'Search',
3 | 'component.globalHeader.search.example1': 'Search example 1',
4 | 'component.globalHeader.search.example2': 'Search example 2',
5 | 'component.globalHeader.search.example3': 'Search example 3',
6 | 'component.globalHeader.help': 'Help',
7 | 'component.globalHeader.notification': 'Notification',
8 | 'component.globalHeader.notification.empty': 'You have viewed all notifications.',
9 | 'component.globalHeader.message': 'Message',
10 | 'component.globalHeader.message.empty': 'You have viewed all messsages.',
11 | 'component.globalHeader.event': 'Event',
12 | 'component.globalHeader.event.empty': 'You have viewed all events.',
13 | 'component.noticeIcon.clear': 'Clear',
14 | 'component.noticeIcon.cleared': 'Cleared',
15 | 'component.noticeIcon.empty': 'No notifications',
16 | 'component.noticeIcon.view-more': 'View more',
17 | };
18 |
--------------------------------------------------------------------------------
/src/locales/en-US/menu.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.welcome': 'Welcome',
3 | 'menu.more-blocks': 'More Blocks',
4 | 'menu.home': 'Home',
5 | 'menu.admin': 'admin',
6 | 'menu.login': 'Login',
7 | 'menu.register': 'Register',
8 | 'menu.register.result': 'Register Result',
9 | 'menu.dashboard': 'Dashboard',
10 | 'menu.dashboard.analysis': 'Analysis',
11 | 'menu.dashboard.monitor': 'Monitor',
12 | 'menu.dashboard.workplace': 'Workplace',
13 | 'menu.exception.403': '403',
14 | 'menu.exception.404': '404',
15 | 'menu.exception.500': '500',
16 | 'menu.form': 'Form',
17 | 'menu.form.basic-form': 'Basic Form',
18 | 'menu.form.step-form': 'Step Form',
19 | 'menu.form.step-form.info': 'Step Form(write transfer information)',
20 | 'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
21 | 'menu.form.step-form.result': 'Step Form(finished)',
22 | 'menu.form.advanced-form': 'Advanced Form',
23 | 'menu.list': 'List',
24 | 'menu.list.table-list': 'Search Table',
25 | 'menu.list.basic-list': 'Basic List',
26 | 'menu.list.card-list': 'Card List',
27 | 'menu.list.search-list': 'Search List',
28 | 'menu.list.search-list.articles': 'Search List(articles)',
29 | 'menu.list.search-list.projects': 'Search List(projects)',
30 | 'menu.list.search-list.applications': 'Search List(applications)',
31 | 'menu.profile': 'Profile',
32 | 'menu.profile.basic': 'Basic Profile',
33 | 'menu.profile.advanced': 'Advanced Profile',
34 | 'menu.result': 'Result',
35 | 'menu.result.success': 'Success',
36 | 'menu.result.fail': 'Fail',
37 | 'menu.exception': 'Exception',
38 | 'menu.exception.not-permission': '403',
39 | 'menu.exception.not-find': '404',
40 | 'menu.exception.server-error': '500',
41 | 'menu.exception.trigger': 'Trigger',
42 | 'menu.account': 'Account',
43 | 'menu.account.center': 'Account Center',
44 | 'menu.account.settings': 'Account Settings',
45 | 'menu.account.trigger': 'Trigger Error',
46 | 'menu.account.logout': 'Logout',
47 | 'menu.account.password': 'Security',
48 | 'menu.editor': 'Graphic Editor',
49 | 'menu.editor.flow': 'Flow Editor',
50 | 'menu.editor.mind': 'Mind Editor',
51 | 'menu.editor.koni': 'Koni Editor',
52 | 'menu.demo': 'Demo Data',
53 | 'menu.user': 'Users',
54 | 'menu.system': 'System',
55 | 'menu.system.menu': 'Menus',
56 | 'menu.system.role': 'Roles',
57 |
58 | 'menu.field.name': 'Name',
59 | 'menu.field.sequence': 'Sequence',
60 | 'menu.field.icon': 'Icon',
61 | 'menu.field.router': 'Route',
62 | 'menu.field.hidden': 'Visible',
63 | 'menu.field.parentID': 'Parent ID',
64 | 'menu.field.parentPath': 'Path',
65 | 'menu.field.creator': 'Creator',
66 |
67 | 'menu.field.parentID.none': 'None',
68 |
69 | 'menu.operation.delete.refuse': 'Refusing to Delete',
70 | 'menu.operation.delete.refuse.preset': 'Refusing to Delete Preset',
71 | 'menu.operation.delete.refuse.submenu': 'Please delete the submenu under the menu first',
72 | 'menu.operation.delete.refuse.confirm': 'Confirm delete this menu?',
73 | 'menu.operation.delete.refuse.confirm.name': 'Confirm deletion please enter menu name',
74 |
75 | 'menu(s)': 'Menu(s)',
76 | 'menu.name': 'Menus',
77 | 'menu.perm': 'Menu permissions',
78 |
79 | 'menu.action.title': 'Actions',
80 | 'menu.action.perm': 'Actions',
81 | 'menu.action.code': 'Code',
82 | 'menu.action.name': 'Name',
83 | 'menu.action.template': 'Action Template',
84 |
85 | 'menu.resource.title': 'Resources',
86 | 'menu.resource.perm': 'Resources',
87 | 'menu.resource.code': 'Code',
88 | 'menu.resource.name': 'Name',
89 | 'menu.resource.name.example': 'e.g.: User Data',
90 | 'menu.resource.method': 'Method',
91 | 'menu.resource.uri': 'URI',
92 | 'menu.resource.uri.example': 'e.g.: /api/v1/users',
93 | 'menu.resource.template': 'Resource Template',
94 |
95 | 'menu.resource.query': '{content} Query',
96 | 'menu.resource.get': '{content} Get',
97 | 'menu.resource.create': '{content} Create',
98 | 'menu.resource.update': '{content} Update',
99 | 'menu.resource.delete': '{content} Delete',
100 | 'menu.resource.enable': '{content} Enable',
101 | 'menu.resource.disable': '{content} Disable',
102 | };
103 |
--------------------------------------------------------------------------------
/src/locales/en-US/pwa.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.pwa.offline': 'You are offline now',
3 | 'app.pwa.serviceworker.updated': 'New content is available',
4 | 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
5 | 'app.pwa.serviceworker.updated.ok': 'Refresh',
6 | };
7 |
--------------------------------------------------------------------------------
/src/locales/en-US/role.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'role(s)': 'Role(s)',
3 | 'role.title': 'Role',
4 | 'role.field.name': 'Name',
5 | 'role.field.sequence': 'Sequence',
6 | 'role.field.memo': 'Memo',
7 | 'role.field.creator': 'Creator',
8 |
9 | 'role.operation.delete.refuse.confirm': 'Confirm delete this menu?',
10 | };
11 |
--------------------------------------------------------------------------------
/src/locales/en-US/settingDrawer.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.setting.pagestyle': 'Page style setting',
3 | 'app.setting.pagestyle.dark': 'Dark style',
4 | 'app.setting.pagestyle.light': 'Light style',
5 | 'app.setting.content-width': 'Content Width',
6 | 'app.setting.content-width.fixed': 'Fixed',
7 | 'app.setting.content-width.fluid': 'Fluid',
8 | 'app.setting.themecolor': 'Theme Color',
9 | 'app.setting.themecolor.dust': 'Dust Red',
10 | 'app.setting.themecolor.volcano': 'Volcano',
11 | 'app.setting.themecolor.sunset': 'Sunset Orange',
12 | 'app.setting.themecolor.cyan': 'Cyan',
13 | 'app.setting.themecolor.green': 'Polar Green',
14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
15 | 'app.setting.themecolor.geekblue': 'Geek Glue',
16 | 'app.setting.themecolor.purple': 'Golden Purple',
17 | 'app.setting.navigationmode': 'Navigation Mode',
18 | 'app.setting.sidemenu': 'Side Menu Layout',
19 | 'app.setting.topmenu': 'Top Menu Layout',
20 | 'app.setting.fixedheader': 'Fixed Header',
21 | 'app.setting.fixedsidebar': 'Fixed Sidebar',
22 | 'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
23 | 'app.setting.hideheader': 'Hidden Header when scrolling',
24 | 'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
25 | 'app.setting.othersettings': 'Other Settings',
26 | 'app.setting.weakmode': 'Weak Mode',
27 | 'app.setting.copy': 'Copy Setting',
28 | 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
29 | 'app.setting.production.hint':
30 | 'Setting panel shows in development environment only, please manually modify',
31 | };
32 |
--------------------------------------------------------------------------------
/src/locales/en-US/settings.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.settings.menuMap.basic': 'Basic Settings',
3 | 'app.settings.menuMap.security': 'Security Settings',
4 | 'app.settings.menuMap.binding': 'Account Binding',
5 | 'app.settings.menuMap.notification': 'New Message Notification',
6 | 'app.settings.basic.avatar': 'Avatar',
7 | 'app.settings.basic.change-avatar': 'Change avatar',
8 | 'app.settings.basic.email': 'Email',
9 | 'app.settings.basic.email-message': 'Please input your email!',
10 | 'app.settings.basic.nickname': 'Nickname',
11 | 'app.settings.basic.nickname-message': 'Please input your Nickname!',
12 | 'app.settings.basic.profile': 'Personal profile',
13 | 'app.settings.basic.profile-message': 'Please input your personal profile!',
14 | 'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
15 | 'app.settings.basic.country': 'Country/Region',
16 | 'app.settings.basic.country-message': 'Please input your country!',
17 | 'app.settings.basic.geographic': 'Province or city',
18 | 'app.settings.basic.geographic-message': 'Please input your geographic info!',
19 | 'app.settings.basic.address': 'Street Address',
20 | 'app.settings.basic.address-message': 'Please input your address!',
21 | 'app.settings.basic.phone': 'Phone Number',
22 | 'app.settings.basic.phone-message': 'Please input your phone!',
23 | 'app.settings.basic.update': 'Update Information',
24 | 'app.settings.security.strong': 'Strong',
25 | 'app.settings.security.medium': 'Medium',
26 | 'app.settings.security.weak': 'Weak',
27 | 'app.settings.security.password': 'Account Password',
28 | 'app.settings.security.password-description': 'Current password strength',
29 | 'app.settings.security.phone': 'Security Phone',
30 | 'app.settings.security.phone-description': 'Bound phone',
31 | 'app.settings.security.question': 'Security Question',
32 | 'app.settings.security.question-description':
33 | 'The security question is not set, and the security policy can effectively protect the account security',
34 | 'app.settings.security.email': 'Backup Email',
35 | 'app.settings.security.email-description': 'Bound Email',
36 | 'app.settings.security.mfa': 'MFA Device',
37 | 'app.settings.security.mfa-description':
38 | 'Unbound MFA device, after binding, can be confirmed twice',
39 | 'app.settings.security.modify': 'Modify',
40 | 'app.settings.security.set': 'Set',
41 | 'app.settings.security.bind': 'Bind',
42 | 'app.settings.binding.taobao': 'Binding Taobao',
43 | 'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
44 | 'app.settings.binding.alipay': 'Binding Alipay',
45 | 'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
46 | 'app.settings.binding.dingding': 'Binding DingTalk',
47 | 'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
48 | 'app.settings.binding.bind': 'Bind',
49 | 'app.settings.notification.password': 'Account Password',
50 | 'app.settings.notification.password-description':
51 | 'Messages from other users will be notified in the form of a station letter',
52 | 'app.settings.notification.messages': 'System Messages',
53 | 'app.settings.notification.messages-description':
54 | 'System messages will be notified in the form of a station letter',
55 | 'app.settings.notification.todo': 'To-do Notification',
56 | 'app.settings.notification.todo-description':
57 | 'The to-do list will be notified in the form of a letter from the station',
58 | 'app.settings.open': 'Open',
59 | 'app.settings.close': 'Close',
60 | };
61 |
--------------------------------------------------------------------------------
/src/locales/en-US/user.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'user.title': 'User',
3 | 'user(s)': 'User(s)',
4 | 'user.field.userName': 'UserName',
5 | 'user.field.realName': 'RealName',
6 | 'user.field.roles': 'Roles',
7 | 'user.field.password': 'Password',
8 | 'user.field.email': 'E-mail',
9 | 'user.field.phone': 'PhoneNumber',
10 | 'user.field.status': 'Status',
11 | 'user.field.creator': 'Creator',
12 | 'user.field.createdAt': 'Created At',
13 | 'user.search.roles': 'Roles',
14 |
15 | 'user.password.change': 'Change password',
16 | 'user.password.update': 'Update password',
17 | 'user.password.oldPassword': 'Old password',
18 | 'user.password.newPassword': 'New password',
19 | 'user.password.confirmPassword': 'Confirm new password',
20 | 'user.password.desc': 'Make sure it\'s at least 15 characters OR at least 8 characters including a number and a lowercase letter.',
21 | 'user.password.notMatch': 'Password confirmation doesn\'t match the password',
22 | };
23 |
--------------------------------------------------------------------------------
/src/locales/pt-BR.js:
--------------------------------------------------------------------------------
1 | import component from './pt-BR/component';
2 | import globalHeader from './pt-BR/globalHeader';
3 | import menu from './pt-BR/menu';
4 | import pwa from './pt-BR/pwa';
5 | import settingDrawer from './pt-BR/settingDrawer';
6 | import settings from './pt-BR/settings';
7 | export default {
8 | 'navBar.lang': 'Idiomas',
9 | 'layout.user.link.help': 'ajuda',
10 | 'layout.user.link.privacy': 'política de privacidade',
11 | 'layout.user.link.terms': 'termos de serviços',
12 | 'app.preview.down.block': 'Download this page to your local project',
13 | ...globalHeader,
14 | ...menu,
15 | ...settingDrawer,
16 | ...settings,
17 | ...pwa,
18 | ...component,
19 | };
20 |
--------------------------------------------------------------------------------
/src/locales/pt-BR/component.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.tagSelect.expand': 'Expandir',
3 | 'component.tagSelect.collapse': 'Diminuir',
4 | 'component.tagSelect.all': 'Todas',
5 | };
6 |
--------------------------------------------------------------------------------
/src/locales/pt-BR/globalHeader.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.globalHeader.search': 'Busca',
3 | 'component.globalHeader.search.example1': 'Exemplo de busca 1',
4 | 'component.globalHeader.search.example2': 'Exemplo de busca 2',
5 | 'component.globalHeader.search.example3': 'Exemplo de busca 3',
6 | 'component.globalHeader.help': 'Ajuda',
7 | 'component.globalHeader.notification': 'Notificação',
8 | 'component.globalHeader.notification.empty': 'Você visualizou todas as notificações.',
9 | 'component.globalHeader.message': 'Mensagem',
10 | 'component.globalHeader.message.empty': 'Você visualizou todas as mensagens.',
11 | 'component.globalHeader.event': 'Evento',
12 | 'component.globalHeader.event.empty': 'Você visualizou todos os eventos.',
13 | 'component.noticeIcon.clear': 'Limpar',
14 | 'component.noticeIcon.cleared': 'Limpo',
15 | 'component.noticeIcon.empty': 'Sem notificações',
16 | 'component.noticeIcon.loaded': 'Carregado',
17 | 'component.noticeIcon.view-more': 'Veja mais',
18 | };
19 |
--------------------------------------------------------------------------------
/src/locales/pt-BR/menu.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.welcome': 'Welcome',
3 | 'menu.more-blocks': 'More Blocks',
4 | 'menu.home': 'Início',
5 | 'menu.login': 'Login',
6 | 'menu.admin': 'admin',
7 | 'menu.register': 'Registro',
8 | 'menu.register.result': 'Resultado de registro',
9 | 'menu.dashboard': 'Dashboard',
10 | 'menu.dashboard.analysis': 'Análise',
11 | 'menu.dashboard.monitor': 'Monitor',
12 | 'menu.dashboard.workplace': 'Ambiente de Trabalho',
13 | 'menu.exception.403': '403',
14 | 'menu.exception.404': '404',
15 | 'menu.exception.500': '500',
16 | 'menu.form': 'Formulário',
17 | 'menu.form.basic-form': 'Formulário Básico',
18 | 'menu.form.step-form': 'Formulário Assistido',
19 | 'menu.form.step-form.info': 'Formulário Assistido(gravar informações de transferência)',
20 | 'menu.form.step-form.confirm': 'Formulário Assistido(confirmar informações de transferência)',
21 | 'menu.form.step-form.result': 'Formulário Assistido(finalizado)',
22 | 'menu.form.advanced-form': 'Formulário Avançado',
23 | 'menu.list': 'Lista',
24 | 'menu.list.table-list': 'Tabela de Busca',
25 | 'menu.list.basic-list': 'Lista Básica',
26 | 'menu.list.card-list': 'Lista de Card',
27 | 'menu.list.search-list': 'Lista de Busca',
28 | 'menu.list.search-list.articles': 'Lista de Busca(artigos)',
29 | 'menu.list.search-list.projects': 'Lista de Busca(projetos)',
30 | 'menu.list.search-list.applications': 'Lista de Busca(aplicações)',
31 | 'menu.profile': 'Perfil',
32 | 'menu.profile.basic': 'Perfil Básico',
33 | 'menu.profile.advanced': 'Perfil Avançado',
34 | 'menu.result': 'Resultado',
35 | 'menu.result.success': 'Sucesso',
36 | 'menu.result.fail': 'Falha',
37 | 'menu.exception': 'Exceção',
38 | 'menu.exception.not-permission': '403',
39 | 'menu.exception.not-find': '404',
40 | 'menu.exception.server-error': '500',
41 | 'menu.exception.trigger': 'Disparar',
42 | 'menu.account': 'Conta',
43 | 'menu.account.center': 'Central da Conta',
44 | 'menu.account.settings': 'Configurar Conta',
45 | 'menu.account.trigger': 'Disparar Erro',
46 | 'menu.account.logout': 'Sair',
47 | 'menu.editor': 'Graphic Editor',
48 | 'menu.editor.flow': 'Flow Editor',
49 | 'menu.editor.mind': 'Mind Editor',
50 | 'menu.editor.koni': 'Koni Editor',
51 | };
52 |
--------------------------------------------------------------------------------
/src/locales/pt-BR/pwa.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.pwa.offline': 'Você está offline agora',
3 | 'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível',
4 | 'app.pwa.serviceworker.updated.hint':
5 | 'Por favor, pressione o botão "Atualizar" para recarregar a página atual',
6 | 'app.pwa.serviceworker.updated.ok': 'Atualizar',
7 | };
8 |
--------------------------------------------------------------------------------
/src/locales/pt-BR/settingDrawer.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.setting.pagestyle': 'Configuração de estilo da página',
3 | 'app.setting.pagestyle.dark': 'Dark style',
4 | 'app.setting.pagestyle.light': 'Light style',
5 | 'app.setting.content-width': 'Largura do conteúdo',
6 | 'app.setting.content-width.fixed': 'Fixo',
7 | 'app.setting.content-width.fluid': 'Fluido',
8 | 'app.setting.themecolor': 'Cor do Tema',
9 | 'app.setting.themecolor.dust': 'Dust Red',
10 | 'app.setting.themecolor.volcano': 'Volcano',
11 | 'app.setting.themecolor.sunset': 'Sunset Orange',
12 | 'app.setting.themecolor.cyan': 'Cyan',
13 | 'app.setting.themecolor.green': 'Polar Green',
14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
15 | 'app.setting.themecolor.geekblue': 'Geek Glue',
16 | 'app.setting.themecolor.purple': 'Golden Purple',
17 | 'app.setting.navigationmode': 'Modo de Navegação',
18 | 'app.setting.sidemenu': 'Layout do Menu Lateral',
19 | 'app.setting.topmenu': 'Layout do Menu Superior',
20 | 'app.setting.fixedheader': 'Cabeçalho fixo',
21 | 'app.setting.fixedsidebar': 'Barra lateral fixa',
22 | 'app.setting.fixedsidebar.hint': 'Funciona no layout do menu lateral',
23 | 'app.setting.hideheader': 'Esconder o cabeçalho quando rolar',
24 | 'app.setting.hideheader.hint': 'Funciona quando o esconder cabeçalho está abilitado',
25 | 'app.setting.othersettings': 'Outras configurações',
26 | 'app.setting.weakmode': 'Weak Mode',
27 | 'app.setting.copy': 'Copiar Configuração',
28 | 'app.setting.copyinfo':
29 | 'copiado com sucesso,por favor trocar o defaultSettings em src/models/setting.js',
30 | 'app.setting.production.hint':
31 | 'O painel de configuração apenas é exibido no ambiente de desenvolvimento, por favor modifique manualmente o',
32 | };
33 |
--------------------------------------------------------------------------------
/src/locales/pt-BR/settings.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.settings.menuMap.basic': 'Configurações Básicas',
3 | 'app.settings.menuMap.security': 'Configurações de Segurança',
4 | 'app.settings.menuMap.binding': 'Vinculação de Conta',
5 | 'app.settings.menuMap.notification': 'Mensagens de Notificação',
6 | 'app.settings.basic.avatar': 'Avatar',
7 | 'app.settings.basic.change-avatar': 'Alterar avatar',
8 | 'app.settings.basic.email': 'Email',
9 | 'app.settings.basic.email-message': 'Por favor insira seu email!',
10 | 'app.settings.basic.nickname': 'Nome de usuário',
11 | 'app.settings.basic.nickname-message': 'Por favor insira seu nome de usuário!',
12 | 'app.settings.basic.profile': 'Perfil pessoal',
13 | 'app.settings.basic.profile-message': 'Por favor insira seu perfil pessoal!',
14 | 'app.settings.basic.profile-placeholder': 'Breve introdução sua',
15 | 'app.settings.basic.country': 'País/Região',
16 | 'app.settings.basic.country-message': 'Por favor insira país!',
17 | 'app.settings.basic.geographic': 'Província, estado ou cidade',
18 | 'app.settings.basic.geographic-message': 'Por favor insira suas informações geográficas!',
19 | 'app.settings.basic.address': 'Endereço',
20 | 'app.settings.basic.address-message': 'Por favor insira seu endereço!',
21 | 'app.settings.basic.phone': 'Número de telefone',
22 | 'app.settings.basic.phone-message': 'Por favor insira seu número de telefone!',
23 | 'app.settings.basic.update': 'Atualizar Informações',
24 | 'app.settings.security.strong': 'Forte',
25 | 'app.settings.security.medium': 'Média',
26 | 'app.settings.security.weak': 'Fraca',
27 | 'app.settings.security.password': 'Senha da Conta',
28 | 'app.settings.security.password-description': 'Força da senha',
29 | 'app.settings.security.phone': 'Telefone de Seguraça',
30 | 'app.settings.security.phone-description': 'Telefone vinculado',
31 | 'app.settings.security.question': 'Pergunta de Segurança',
32 | 'app.settings.security.question-description':
33 | 'A pergunta de segurança não está definida e a política de segurança pode proteger efetivamente a segurança da conta',
34 | 'app.settings.security.email': 'Email de Backup',
35 | 'app.settings.security.email-description': 'Email vinculado',
36 | 'app.settings.security.mfa': 'Dispositivo MFA',
37 | 'app.settings.security.mfa-description':
38 | 'O dispositivo MFA não vinculado, após a vinculação, pode ser confirmado duas vezes',
39 | 'app.settings.security.modify': 'Modificar',
40 | 'app.settings.security.set': 'Atribuir',
41 | 'app.settings.security.bind': 'Vincular',
42 | 'app.settings.binding.taobao': 'Vincular Taobao',
43 | 'app.settings.binding.taobao-description': 'Atualmente não vinculado à conta Taobao',
44 | 'app.settings.binding.alipay': 'Vincular Alipay',
45 | 'app.settings.binding.alipay-description': 'Atualmente não vinculado à conta Alipay',
46 | 'app.settings.binding.dingding': 'Vincular DingTalk',
47 | 'app.settings.binding.dingding-description': 'Atualmente não vinculado à conta DingTalk',
48 | 'app.settings.binding.bind': 'Vincular',
49 | 'app.settings.notification.password': 'Senha da Conta',
50 | 'app.settings.notification.password-description':
51 | 'Mensagens de outros usuários serão notificadas na forma de uma estação de letra',
52 | 'app.settings.notification.messages': 'Mensagens de Sistema',
53 | 'app.settings.notification.messages-description':
54 | 'Mensagens de sistema serão notificadas na forma de uma estação de letra',
55 | 'app.settings.notification.todo': 'Notificação de To-do',
56 | 'app.settings.notification.todo-description':
57 | 'A lista de to-do será notificada na forma de uma estação de letra',
58 | 'app.settings.open': 'Aberto',
59 | 'app.settings.close': 'Fechado',
60 | };
61 |
--------------------------------------------------------------------------------
/src/locales/zh-CN.js:
--------------------------------------------------------------------------------
1 | import component from './zh-CN/component';
2 | import globalHeader from './zh-CN/globalHeader';
3 | import menu from './zh-CN/menu';
4 | import user from './zh-CN/user';
5 | import role from './zh-CN/role';
6 | import pwa from './zh-CN/pwa';
7 | import settingDrawer from './zh-CN/settingDrawer';
8 | import settings from './zh-CN/settings';
9 | export default {
10 | 'navBar.lang': '语言',
11 | 'layout.user.link.help': '帮助',
12 | 'layout.user.link.privacy': '隐私',
13 | 'layout.user.link.terms': '条款',
14 | 'app.name': '权限管理脚手架',
15 | 'app.desc': 'gin-admin-react 基于 Ant Design Pro v4',
16 | 'app.preview.down.block': '下载此页面到本地项目',
17 | 'app.welcome.title': 'gin-admin v5.2.0 现已发布,欢迎使用 gin-admin-cli 启动体验。',
18 | 'app.welcome.link.gin-admin-react': '测试中遇到问题,请先保证 gin-admin 版本为最新版。仍有问题,请提 issue',
19 | 'app.welcome.project.gin-admin': '使用 gin-admin-cli 快速构建 gin-admin',
20 | ...globalHeader,
21 | ...menu,
22 | ...user,
23 | ...role,
24 | ...settingDrawer,
25 | ...settings,
26 | ...pwa,
27 | ...component,
28 | };
29 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/component.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.tagSelect.expand': '展开',
3 | 'component.tagSelect.collapse': '收起',
4 | 'component.tagSelect.all': '全部',
5 | 'component.name:': '名称:',
6 | 'component.name:.content': '名称:{content}',
7 | 'component.button.confirm': '确认',
8 | 'component.button.cancel': '取消',
9 | 'component.template': '模板',
10 | 'component.template.basic': '简单',
11 | 'component.template.advanced': '高级',
12 | 'component.option.all': '全部',
13 | 'component.option.normal': '正常',
14 | 'component.option.ban': '停用',
15 | 'component.option.show': '显示',
16 | 'component.option.hidden': '隐藏',
17 | 'component.operation': '操作',
18 | 'component.operation.edit': '编辑',
19 | 'component.operation.create': '新增',
20 | 'component.operation.delete': '删除',
21 | 'component.operation.update': '更新',
22 | 'component.operation.query': '查询',
23 | 'component.operation.enable': '启用',
24 | 'component.operation.disable': '禁用',
25 | 'component.operation.create.content': '新增{content}',
26 | 'component.operation.delete.content': '删除{content}',
27 | 'component.operation.update.content': '编辑{content}',
28 | 'component.operation.enable.content': '启用{content}',
29 | 'component.operation.disable.content': '禁用{content}',
30 | 'component.operation.delete.confirm': '确认删除?',
31 | 'component.operation.reset': '重置',
32 | 'component.searchList.search': '查询',
33 | 'component.searchList.reset': '重置',
34 | 'component.searchList.status': '状态',
35 | 'component.searchList.result.total': '共 {total} 条',
36 | 'component.placeholder': '请输入',
37 | 'component.placeholder.select': '请选择',
38 | 'component.placeholder.content': '请输入{content}',
39 | 'component.placeholder.select.content': '请选择{content}',
40 | 'component.notification.request.success': '成功',
41 | 'component.notification.request.fail': '错误',
42 | 'component.notification.request.success.content': '{content}成功',
43 | 'component.notification.request.fail.content': '{content}错误',
44 | 'component.icon.only.support.official': '图标仅支持官方Icon图标',
45 | 'component.order.description.desc': '降序排列,越大越靠前',
46 | 'component.order.description.asc': '升序排列,越小越靠前',
47 | };
48 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/globalHeader.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.globalHeader.search': '站内搜索',
3 | 'component.globalHeader.search.example1': '搜索提示一',
4 | 'component.globalHeader.search.example2': '搜索提示二',
5 | 'component.globalHeader.search.example3': '搜索提示三',
6 | 'component.globalHeader.help': '使用文档',
7 | 'component.globalHeader.notification': '通知',
8 | 'component.globalHeader.notification.empty': '你已查看所有通知',
9 | 'component.globalHeader.message': '消息',
10 | 'component.globalHeader.message.empty': '您已读完所有消息',
11 | 'component.globalHeader.event': '待办',
12 | 'component.globalHeader.event.empty': '你已完成所有待办',
13 | 'component.noticeIcon.clear': '清空',
14 | 'component.noticeIcon.cleared': '清空了',
15 | 'component.noticeIcon.empty': '暂无数据',
16 | 'component.noticeIcon.view-more': '查看更多',
17 | };
18 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/menu.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.welcome': '欢迎',
3 | 'menu.more-blocks': '更多区块',
4 | 'menu.home': '首页',
5 | 'menu.admin': '管理页',
6 | 'menu.login': '登录',
7 | 'menu.register': '注册',
8 | 'menu.register.result': '注册结果',
9 | 'menu.dashboard': 'Dashboard',
10 | 'menu.dashboard.analysis': '分析页',
11 | 'menu.dashboard.monitor': '监控页',
12 | 'menu.dashboard.workplace': '工作台',
13 | 'menu.exception.403': '403',
14 | 'menu.exception.404': '404',
15 | 'menu.exception.500': '500',
16 | 'menu.form': '表单页',
17 | 'menu.form.basic-form': '基础表单',
18 | 'menu.form.step-form': '分步表单',
19 | 'menu.form.step-form.info': '分步表单(填写转账信息)',
20 | 'menu.form.step-form.confirm': '分步表单(确认转账信息)',
21 | 'menu.form.step-form.result': '分步表单(完成)',
22 | 'menu.form.advanced-form': '高级表单',
23 | 'menu.list': '列表页',
24 | 'menu.list.table-list': '查询表格',
25 | 'menu.list.basic-list': '标准列表',
26 | 'menu.list.card-list': '卡片列表',
27 | 'menu.list.search-list': '搜索列表',
28 | 'menu.list.search-list.articles': '搜索列表(文章)',
29 | 'menu.list.search-list.projects': '搜索列表(项目)',
30 | 'menu.list.search-list.applications': '搜索列表(应用)',
31 | 'menu.profile': '详情页',
32 | 'menu.profile.basic': '基础详情页',
33 | 'menu.profile.advanced': '高级详情页',
34 | 'menu.result': '结果页',
35 | 'menu.result.success': '成功页',
36 | 'menu.result.fail': '失败页',
37 | 'menu.exception': '异常页',
38 | 'menu.exception.not-permission': '403',
39 | 'menu.exception.not-find': '404',
40 | 'menu.exception.server-error': '500',
41 | 'menu.exception.trigger': '触发错误',
42 | 'menu.account': '个人页',
43 | 'menu.account.center': '个人中心',
44 | 'menu.account.settings': '个人设置',
45 | 'menu.account.trigger': '触发报错',
46 | 'menu.account.logout': '退出登录',
47 | 'menu.account.password': '修改密码',
48 | 'menu.editor': '图形编辑器',
49 | 'menu.editor.flow': '流程编辑器',
50 | 'menu.editor.mind': '脑图编辑器',
51 | 'menu.editor.koni': '拓扑编辑器',
52 | 'menu.demo': '演示数据',
53 | 'menu.user': '用户',
54 | 'menu.system': '系统配置',
55 | 'menu.system.menu': '菜单管理',
56 | 'menu.system.role': '角色管理',
57 |
58 | 'menu.field.name': '菜单名称',
59 | 'menu.field.sequence': '排序值',
60 | 'menu.field.icon': '菜单图标',
61 | 'menu.field.router': '访问路由',
62 | 'menu.field.hidden': '可见状态',
63 | 'menu.field.parentID': '父级内码',
64 | 'menu.field.parentPath': '父级路径',
65 | 'menu.field.creator': '创建人',
66 |
67 | 'menu.field.parentID.none': '无上级目录',
68 |
69 | 'menu.operation.delete.refuse': '禁止删除菜单',
70 | 'menu.operation.delete.refuse.preset': '禁止删除预设菜单',
71 | 'menu.operation.delete.refuse.submenu': '请先删除菜单下的子菜单',
72 | 'menu.operation.delete.refuse.confirm': '确认删除这个菜单?',
73 | 'menu.operation.delete.refuse.confirm.name': '确认删除请输入菜单名称',
74 |
75 | 'menu(s)': '菜单',
76 | 'menu.name': '菜单名称',
77 | 'menu.perm': '菜单权限',
78 |
79 | 'menu.action.title': '动作管理',
80 | 'menu.action.perm': '动作权限',
81 | 'menu.action.code': '动作编号',
82 | 'menu.action.name': '动作名称',
83 | 'menu.action.template': '资源模板',
84 |
85 | 'menu.resource.title': '资源管理',
86 | 'menu.resource.perm': '资源权限',
87 | 'menu.resource.code': '资源编号',
88 | 'menu.resource.name': '资源名称',
89 | 'menu.resource.name.example': '例:用户数据',
90 | 'menu.resource.method': '请求方式',
91 | 'menu.resource.uri': 'URI',
92 | 'menu.resource.uri.example': '例:/api/v1/users',
93 | 'menu.resource.template': '资源模板',
94 |
95 | 'menu.resource.query': '查询{content}',
96 | 'menu.resource.get': '精确查询{content}',
97 | 'menu.resource.create': '创建{content}',
98 | 'menu.resource.update': '更新{content}',
99 | 'menu.resource.delete': '删除{content}',
100 | 'menu.resource.enable': '启用{content}',
101 | 'menu.resource.disable': '禁用{content}',
102 | };
103 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/pwa.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.pwa.offline': '当前处于离线状态',
3 | 'app.pwa.serviceworker.updated': '有新内容',
4 | 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
5 | 'app.pwa.serviceworker.updated.ok': '刷新',
6 | };
7 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/role.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'role(s)': '角色',
3 | 'role.title': '角色',
4 | 'role.field.name': '角色名称',
5 | 'role.field.sequence': '排列顺序',
6 | 'role.field.memo': '备注',
7 | 'role.field.creator': '创建者',
8 |
9 | 'role.operation.delete.refuse.confirm': '确认删除这个菜单?',
10 | };
11 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/settingDrawer.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.setting.pagestyle': '整体风格设置',
3 | 'app.setting.pagestyle.dark': '暗色菜单风格',
4 | 'app.setting.pagestyle.light': '亮色菜单风格',
5 | 'app.setting.content-width': '内容区域宽度',
6 | 'app.setting.content-width.fixed': '定宽',
7 | 'app.setting.content-width.fluid': '流式',
8 | 'app.setting.themecolor': '主题色',
9 | 'app.setting.themecolor.dust': '薄暮',
10 | 'app.setting.themecolor.volcano': '火山',
11 | 'app.setting.themecolor.sunset': '日暮',
12 | 'app.setting.themecolor.cyan': '明青',
13 | 'app.setting.themecolor.green': '极光绿',
14 | 'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
15 | 'app.setting.themecolor.geekblue': '极客蓝',
16 | 'app.setting.themecolor.purple': '酱紫',
17 | 'app.setting.navigationmode': '导航模式',
18 | 'app.setting.sidemenu': '侧边菜单布局',
19 | 'app.setting.topmenu': '顶部菜单布局',
20 | 'app.setting.fixedheader': '固定 Header',
21 | 'app.setting.fixedsidebar': '固定侧边菜单',
22 | 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
23 | 'app.setting.hideheader': '下滑时隐藏 Header',
24 | 'app.setting.hideheader.hint': '固定 Header 时可配置',
25 | 'app.setting.othersettings': '其他设置',
26 | 'app.setting.weakmode': '色弱模式',
27 | 'app.setting.copy': '拷贝设置',
28 | 'app.setting.copyinfo': '拷贝成功,请到 src/defaultSettings.js 中替换默认配置',
29 | 'app.setting.production.hint':
30 | '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
31 | };
32 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/settings.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.settings.menuMap.basic': '基本设置',
3 | 'app.settings.menuMap.security': '安全设置',
4 | 'app.settings.menuMap.binding': '账号绑定',
5 | 'app.settings.menuMap.notification': '新消息通知',
6 | 'app.settings.basic.avatar': '头像',
7 | 'app.settings.basic.change-avatar': '更换头像',
8 | 'app.settings.basic.email': '邮箱',
9 | 'app.settings.basic.email-message': '请输入您的邮箱!',
10 | 'app.settings.basic.nickname': '昵称',
11 | 'app.settings.basic.nickname-message': '请输入您的昵称!',
12 | 'app.settings.basic.profile': '个人简介',
13 | 'app.settings.basic.profile-message': '请输入个人简介!',
14 | 'app.settings.basic.profile-placeholder': '个人简介',
15 | 'app.settings.basic.country': '国家/地区',
16 | 'app.settings.basic.country-message': '请输入您的国家或地区!',
17 | 'app.settings.basic.geographic': '所在省市',
18 | 'app.settings.basic.geographic-message': '请输入您的所在省市!',
19 | 'app.settings.basic.address': '街道地址',
20 | 'app.settings.basic.address-message': '请输入您的街道地址!',
21 | 'app.settings.basic.phone': '联系电话',
22 | 'app.settings.basic.phone-message': '请输入您的联系电话!',
23 | 'app.settings.basic.update': '更新基本信息',
24 | 'app.settings.security.strong': '强',
25 | 'app.settings.security.medium': '中',
26 | 'app.settings.security.weak': '弱',
27 | 'app.settings.security.password': '账户密码',
28 | 'app.settings.security.password-description': '当前密码强度',
29 | 'app.settings.security.phone': '密保手机',
30 | 'app.settings.security.phone-description': '已绑定手机',
31 | 'app.settings.security.question': '密保问题',
32 | 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
33 | 'app.settings.security.email': '备用邮箱',
34 | 'app.settings.security.email-description': '已绑定邮箱',
35 | 'app.settings.security.mfa': 'MFA 设备',
36 | 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
37 | 'app.settings.security.modify': '修改',
38 | 'app.settings.security.set': '设置',
39 | 'app.settings.security.bind': '绑定',
40 | 'app.settings.binding.taobao': '绑定淘宝',
41 | 'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
42 | 'app.settings.binding.alipay': '绑定支付宝',
43 | 'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
44 | 'app.settings.binding.dingding': '绑定钉钉',
45 | 'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
46 | 'app.settings.binding.bind': '绑定',
47 | 'app.settings.notification.password': '账户密码',
48 | 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
49 | 'app.settings.notification.messages': '系统消息',
50 | 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
51 | 'app.settings.notification.todo': '待办任务',
52 | 'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
53 | 'app.settings.open': '开',
54 | 'app.settings.close': '关',
55 | };
56 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/user.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'user.title': '用户',
3 | 'user(s)': '用户',
4 | 'user.field.userName': '用户名',
5 | 'user.field.realName': '真实姓名',
6 | 'user.field.roles': '角色名称',
7 | 'user.field.password': '密码',
8 | 'user.field.email': '邮箱',
9 | 'user.field.phone': '手机号',
10 | 'user.field.status': '状态',
11 | 'user.field.creator': '创建者',
12 | 'user.field.createdAt': '创建时间',
13 | 'user.search.roles': '所属角色',
14 |
15 | 'user.password.change': '修改密码',
16 | 'user.password.update': '更新用户密码',
17 | 'user.password.oldPassword': '用户原密码',
18 | 'user.password.newPassword': '用户新密码',
19 | 'user.password.confirmPassword': '确认新密码',
20 | 'user.password.desc': 'Make sure it\'s at least 15 characters OR at least 8 characters including a number and a lowercase letter.',
21 | 'user.password.notMatch': '新密码与确认密码不一致',
22 | };
23 |
--------------------------------------------------------------------------------
/src/locales/zh-TW.js:
--------------------------------------------------------------------------------
1 | import component from './zh-TW/component';
2 | import globalHeader from './zh-TW/globalHeader';
3 | import menu from './zh-TW/menu';
4 | import pwa from './zh-TW/pwa';
5 | import settingDrawer from './zh-TW/settingDrawer';
6 | import settings from './zh-TW/settings';
7 | export default {
8 | 'navBar.lang': '語言',
9 | 'layout.user.link.help': '幫助',
10 | 'layout.user.link.privacy': '隱私',
11 | 'layout.user.link.terms': '條款',
12 | 'app.preview.down.block': '下載此頁面到本地項目',
13 | 'app.welcome.title': 'gin-admin v5.2.0 現已發布,歡迎使用 gin-admin-cli 啟動體驗。',
14 | 'app.welcome.link.gin-admin-react': '測試中遇到問題,請先保證 gin-admin 版本為最新版。仍有問題,請提 issue',
15 | 'app.welcome.project.gin-admin': '使用 gin-admin-cli 快速構建 gin-admin',
16 | ...globalHeader,
17 | ...menu,
18 | ...settingDrawer,
19 | ...settings,
20 | ...pwa,
21 | ...component,
22 | };
23 |
--------------------------------------------------------------------------------
/src/locales/zh-TW/component.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.tagSelect.expand': '展開',
3 | 'component.tagSelect.collapse': '收起',
4 | 'component.tagSelect.all': '全部',
5 | };
6 |
--------------------------------------------------------------------------------
/src/locales/zh-TW/globalHeader.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.globalHeader.search': '站內搜索',
3 | 'component.globalHeader.search.example1': '搜索提示壹',
4 | 'component.globalHeader.search.example2': '搜索提示二',
5 | 'component.globalHeader.search.example3': '搜索提示三',
6 | 'component.globalHeader.help': '使用手冊',
7 | 'component.globalHeader.notification': '通知',
8 | 'component.globalHeader.notification.empty': '妳已查看所有通知',
9 | 'component.globalHeader.message': '消息',
10 | 'component.globalHeader.message.empty': '您已讀完所有消息',
11 | 'component.globalHeader.event': '待辦',
12 | 'component.globalHeader.event.empty': '妳已完成所有待辦',
13 | 'component.noticeIcon.clear': '清空',
14 | 'component.noticeIcon.cleared': '清空了',
15 | 'component.noticeIcon.empty': '暫無資料',
16 | 'component.noticeIcon.view-more': '查看更多',
17 | };
18 |
--------------------------------------------------------------------------------
/src/locales/zh-TW/menu.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.welcome': '歡迎',
3 | 'menu.more-blocks': '更多區塊',
4 | 'menu.home': '首頁',
5 | 'menu.login': '登錄',
6 | 'menu.admin': '权限',
7 | 'menu.exception.403': '403',
8 | 'menu.exception.404': '404',
9 | 'menu.exception.500': '500',
10 | 'menu.register': '註冊',
11 | 'menu.register.result': '註冊結果',
12 | 'menu.dashboard': 'Dashboard',
13 | 'menu.dashboard.analysis': '分析頁',
14 | 'menu.dashboard.monitor': '監控頁',
15 | 'menu.dashboard.workplace': '工作臺',
16 | 'menu.form': '表單頁',
17 | 'menu.form.basic-form': '基礎表單',
18 | 'menu.form.step-form': '分步表單',
19 | 'menu.form.step-form.info': '分步表單(填寫轉賬信息)',
20 | 'menu.form.step-form.confirm': '分步表單(確認轉賬信息)',
21 | 'menu.form.step-form.result': '分步表單(完成)',
22 | 'menu.form.advanced-form': '高級表單',
23 | 'menu.list': '列表頁',
24 | 'menu.list.table-list': '查詢表格',
25 | 'menu.list.basic-list': '標淮列表',
26 | 'menu.list.card-list': '卡片列表',
27 | 'menu.list.search-list': '搜索列表',
28 | 'menu.list.search-list.articles': '搜索列表(文章)',
29 | 'menu.list.search-list.projects': '搜索列表(項目)',
30 | 'menu.list.search-list.applications': '搜索列表(應用)',
31 | 'menu.profile': '詳情頁',
32 | 'menu.profile.basic': '基礎詳情頁',
33 | 'menu.profile.advanced': '高級詳情頁',
34 | 'menu.result': '結果頁',
35 | 'menu.result.success': '成功頁',
36 | 'menu.result.fail': '失敗頁',
37 | 'menu.account': '個人頁',
38 | 'menu.account.center': '個人中心',
39 | 'menu.account.settings': '個人設置',
40 | 'menu.account.trigger': '觸發報錯',
41 | 'menu.account.logout': '退出登錄',
42 | 'menu.exception': '异常页',
43 | 'menu.exception.not-permission': '403',
44 | 'menu.exception.not-find': '404',
45 | 'menu.exception.server-error': '500',
46 | 'menu.exception.trigger': '触发错误',
47 | 'menu.editor': '圖形編輯器',
48 | 'menu.editor.flow': '流程編輯器',
49 | 'menu.editor.mind': '腦圖編輯器',
50 | 'menu.editor.koni': '拓撲編輯器',
51 | 'menu.demo': '演示數據',
52 | 'menu.user': '用戶',
53 | 'menu.system': '系統配置',
54 | 'menu.system.menu': '菜單管理',
55 | 'menu.system.role': '角色管理',
56 | };
57 |
--------------------------------------------------------------------------------
/src/locales/zh-TW/pwa.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.pwa.offline': '當前處於離線狀態',
3 | 'app.pwa.serviceworker.updated': '有新內容',
4 | 'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
5 | 'app.pwa.serviceworker.updated.ok': '刷新',
6 | };
7 |
--------------------------------------------------------------------------------
/src/locales/zh-TW/settingDrawer.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.setting.pagestyle': '整體風格設置',
3 | 'app.setting.pagestyle.dark': '暗色菜單風格',
4 | 'app.setting.pagestyle.light': '亮色菜單風格',
5 | 'app.setting.content-width': '內容區域寬度',
6 | 'app.setting.content-width.fixed': '定寬',
7 | 'app.setting.content-width.fluid': '流式',
8 | 'app.setting.themecolor': '主題色',
9 | 'app.setting.themecolor.dust': '薄暮',
10 | 'app.setting.themecolor.volcano': '火山',
11 | 'app.setting.themecolor.sunset': '日暮',
12 | 'app.setting.themecolor.cyan': '明青',
13 | 'app.setting.themecolor.green': '極光綠',
14 | 'app.setting.themecolor.daybreak': '拂曉藍(默認)',
15 | 'app.setting.themecolor.geekblue': '極客藍',
16 | 'app.setting.themecolor.purple': '醬紫',
17 | 'app.setting.navigationmode': '導航模式',
18 | 'app.setting.sidemenu': '側邊菜單布局',
19 | 'app.setting.topmenu': '頂部菜單布局',
20 | 'app.setting.fixedheader': '固定 Header',
21 | 'app.setting.fixedsidebar': '固定側邊菜單',
22 | 'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置',
23 | 'app.setting.hideheader': '下滑時隱藏 Header',
24 | 'app.setting.hideheader.hint': '固定 Header 時可配置',
25 | 'app.setting.othersettings': '其他設置',
26 | 'app.setting.weakmode': '色弱模式',
27 | 'app.setting.copy': '拷貝設置',
28 | 'app.setting.copyinfo': '拷貝成功,請到 src/defaultSettings.js 中替換默認配置',
29 | 'app.setting.production.hint':
30 | '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
31 | };
32 |
--------------------------------------------------------------------------------
/src/locales/zh-TW/settings.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.settings.menuMap.basic': '基本設置',
3 | 'app.settings.menuMap.security': '安全設置',
4 | 'app.settings.menuMap.binding': '賬號綁定',
5 | 'app.settings.menuMap.notification': '新消息通知',
6 | 'app.settings.basic.avatar': '頭像',
7 | 'app.settings.basic.change-avatar': '更換頭像',
8 | 'app.settings.basic.email': '郵箱',
9 | 'app.settings.basic.email-message': '請輸入您的郵箱!',
10 | 'app.settings.basic.nickname': '昵稱',
11 | 'app.settings.basic.nickname-message': '請輸入您的昵稱!',
12 | 'app.settings.basic.profile': '個人簡介',
13 | 'app.settings.basic.profile-message': '請輸入個人簡介!',
14 | 'app.settings.basic.profile-placeholder': '個人簡介',
15 | 'app.settings.basic.country': '國家/地區',
16 | 'app.settings.basic.country-message': '請輸入您的國家或地區!',
17 | 'app.settings.basic.geographic': '所在省市',
18 | 'app.settings.basic.geographic-message': '請輸入您的所在省市!',
19 | 'app.settings.basic.address': '街道地址',
20 | 'app.settings.basic.address-message': '請輸入您的街道地址!',
21 | 'app.settings.basic.phone': '聯系電話',
22 | 'app.settings.basic.phone-message': '請輸入您的聯系電話!',
23 | 'app.settings.basic.update': '更新基本信息',
24 | 'app.settings.security.strong': '強',
25 | 'app.settings.security.medium': '中',
26 | 'app.settings.security.weak': '弱',
27 | 'app.settings.security.password': '賬戶密碼',
28 | 'app.settings.security.password-description': '當前密碼強度',
29 | 'app.settings.security.phone': '密保手機',
30 | 'app.settings.security.phone-description': '已綁定手機',
31 | 'app.settings.security.question': '密保問題',
32 | 'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
33 | 'app.settings.security.email': '備用郵箱',
34 | 'app.settings.security.email-description': '已綁定郵箱',
35 | 'app.settings.security.mfa': 'MFA 設備',
36 | 'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
37 | 'app.settings.security.modify': '修改',
38 | 'app.settings.security.set': '設置',
39 | 'app.settings.security.bind': '綁定',
40 | 'app.settings.binding.taobao': '綁定淘寶',
41 | 'app.settings.binding.taobao-description': '當前未綁定淘寶賬號',
42 | 'app.settings.binding.alipay': '綁定支付寶',
43 | 'app.settings.binding.alipay-description': '當前未綁定支付寶賬號',
44 | 'app.settings.binding.dingding': '綁定釘釘',
45 | 'app.settings.binding.dingding-description': '當前未綁定釘釘賬號',
46 | 'app.settings.binding.bind': '綁定',
47 | 'app.settings.notification.password': '賬戶密碼',
48 | 'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知',
49 | 'app.settings.notification.messages': '系統消息',
50 | 'app.settings.notification.messages-description': '系統消息將以站內信的形式通知',
51 | 'app.settings.notification.todo': '待辦任務',
52 | 'app.settings.notification.todo-description': '待辦任務將以站內信的形式通知',
53 | 'app.settings.open': '開',
54 | 'app.settings.close': '關',
55 | };
56 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "权限管理脚手架",
3 | "short_name": "gin-admin-react",
4 | "display": "standalone",
5 | "start_url": "./?utm_source=homescreen",
6 | "theme_color": "#002140",
7 | "background_color": "#001529",
8 | "icons": [
9 | {
10 | "src": "icons/icon-192x192.png",
11 | "sizes": "192x192"
12 | },
13 | {
14 | "src": "icons/icon-128x128.png",
15 | "sizes": "128x128"
16 | },
17 | {
18 | "src": "icons/icon-512x512.png",
19 | "sizes": "512x512"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/models/login.js:
--------------------------------------------------------------------------------
1 | import { routerRedux } from 'dva/router';
2 | import { stringify } from 'querystring';
3 | import {
4 | accountLogin,
5 | accountLogout,
6 | getCaptcha,
7 | refreshCaptcha,
8 | updatePassword,
9 | } from '@/services/login';
10 | import { getPageQuery } from '@/utils/utils';
11 | import store from '@/utils/store';
12 | import md5 from 'md5';
13 | // import { setAuthority } from '@/utils/authority';
14 |
15 | const Model = {
16 | namespace: 'login',
17 | state: {
18 | status: undefined,
19 | captchaID: '',
20 | },
21 | effects: {
22 | *login({ payload, error }, { call, put }) {
23 | payload.password = md5(payload.password);
24 | const { response, data } = yield call(accountLogin, payload);
25 | yield put({
26 | type: 'changeLoginStatus',
27 | payload: data,
28 | }); // Login successfully
29 |
30 | if (response.status === 200 && data.expires_at && data.expires_at > 0) {
31 | store.setAccessToken(data);
32 |
33 | const urlParams = new URL(window.location.href);
34 | const params = getPageQuery();
35 | let { redirect } = params;
36 |
37 | if (redirect) {
38 | const redirectUrlParams = new URL(redirect);
39 |
40 | if (redirectUrlParams.origin === urlParams.origin) {
41 | redirect = redirect.substr(urlParams.origin.length);
42 |
43 | if (redirect.match(/^\/.*#/)) {
44 | redirect = redirect.substr(redirect.indexOf('#') + 1);
45 | }
46 | } else {
47 | window.location.href = '/';
48 | return;
49 | }
50 | }
51 |
52 | yield put(routerRedux.replace(redirect || '/'));
53 | } else {
54 | if (error) error();
55 | yield put({ type: 'getCaptcha' });
56 | }
57 | },
58 |
59 | *getCaptcha(_, { call, put }) {
60 | const { response, data } = yield call(getCaptcha);
61 | if (response.status === 200) {
62 | yield put({
63 | type: 'saveCaptchaID',
64 | payload: data
65 | })
66 | }
67 | },
68 |
69 | *refreshCaptcha({ payload }, { call }) {
70 | yield call(refreshCaptcha, payload);
71 | },
72 |
73 | *updatePassword({ payload, success }, { call, put }) {
74 | const { response } = yield call(updatePassword, payload);
75 | if (response.status === 200) {
76 | yield put({ type: 'global/requestSuccess', payload: {
77 | id: 'user.field.password', action: 'update',
78 | }});
79 | if (success) success();
80 | }
81 | },
82 |
83 | *logout(_, { call, put }) {
84 | const { response } = yield call(accountLogout);
85 | if (response.status === 200) {
86 | store.clearAccessToken();
87 |
88 | const { redirect } = getPageQuery(); // redirect
89 | if (window.location.pathname !== '/login' && !redirect) {
90 | yield put(
91 | routerRedux.replace({
92 | pathname: '/login',
93 | search: stringify({
94 | redirect: window.location.href,
95 | }),
96 | }),
97 | );
98 | }
99 | }
100 | },
101 | },
102 | reducers: {
103 | changeLoginStatus(state, { payload }) {
104 | // setAuthority(payload.currentAuthority);
105 | return { ...state, status: true };
106 | },
107 | saveCaptchaID(state, { payload }) {
108 | return { ...state, captchaID: payload.captcha_id };
109 | },
110 | },
111 | };
112 | export default Model;
113 |
--------------------------------------------------------------------------------
/src/models/menu.js:
--------------------------------------------------------------------------------
1 | import { create, query, queryTree, get, remove, update } from '@/services/menu';
2 |
3 | const Model = {
4 | namespace: 'menu',
5 | state: {
6 | data: {
7 | list: [],
8 | pagination: {},
9 | },
10 | treeData: [],
11 | },
12 | effects: {
13 | *fetch({ payload, success }, { call, put }) {
14 | const { response, data } = yield call(query, payload);
15 | if (response.status === 200) {
16 | yield put({
17 | type: 'save',
18 | payload: data,
19 | });
20 | yield put({ type: 'fetchTree' });
21 | if (success) success();
22 | }
23 | },
24 |
25 | *get({ payload, success }, { call }) {
26 | const { response, data } = yield call(get, payload);
27 | if (response.status === 200) {
28 | if (success) success(data);
29 | }
30 | },
31 |
32 | *fetchTree({ payload, success }, { call, put }) {
33 | const { response, data } = yield call(queryTree, payload);
34 | if (response.status === 200) {
35 | yield put({
36 | type: 'saveTree',
37 | payload: data.list || [],
38 | });
39 | /* 在角色管理页面可以输出 菜单树 JSON */
40 | // yield put({
41 | // type: 'outputMenus',
42 | // });
43 | if (success) success(data.list);
44 | }
45 | },
46 |
47 | *add({ payload, success }, { call, put }) {
48 | const { response } = yield call(create, payload);
49 | if (response.status === 200) {
50 | yield put({ type: 'global/requestSuccess', payload: {
51 | id: 'menu(s)', action: 'create',
52 | }});
53 | if (success) success();
54 | }
55 | },
56 |
57 | *remove({ payload, success }, { call, put }) {
58 | const { response } = yield call(remove, payload);
59 | if (response.status === 200) {
60 | yield put({ type: 'global/requestSuccess', payload: {
61 | id: 'menu(s)', action: 'delete',
62 | }});
63 | if (success) success();
64 | }
65 | },
66 |
67 | *update({ payload, success }, { call, put }) {
68 | const { response } = yield call(update, payload);
69 | if (response.status === 200) {
70 | yield put({ type: 'global/requestSuccess', payload: {
71 | id: 'menu(s)', action: 'update',
72 | }});
73 | if (success) success();
74 | }
75 | },
76 | },
77 | reducers: {
78 | updateData(state, { payload }) {
79 | const { data } = state;
80 | data.list = data.list.map(item => {
81 | if (item.record_id === payload.record_id) {
82 | return payload;
83 | }
84 | return item;
85 | });
86 | return {
87 | ...state,
88 | data
89 | };
90 | },
91 | save(state, { payload }) {
92 | return {
93 | ...state,
94 | data: payload,
95 | };
96 | },
97 | saveTree(state, { payload }) {
98 | return {
99 | ...state,
100 | treeData: payload,
101 | };
102 | },
103 | /* 在角色管理页面可以输出 菜单树 JSON */
104 | outputMenus(state) {
105 | let menus = JSON.parse(JSON.stringify(state.treeData));
106 | menus.map(menu => {
107 | delete menu['parent_id'];
108 | delete menu['parent_path'];
109 | delete menu['hidden'];
110 | if (menu.children && menu.children.length > 0) {
111 | delete menu['router'];
112 | delete menu['actions'];
113 | delete menu['resources'];
114 | menu.children.map(menu => {
115 | delete menu['parent_id'];
116 | delete menu['parent_path'];
117 | delete menu['children'];
118 | delete menu['record_id'];
119 | delete menu['hidden'];
120 | });
121 | Object.keys(menu.children).sort();
122 | } else {
123 | delete menu['children'];
124 | }
125 |
126 | delete menu['record_id'];
127 | Object.keys(menu).sort();
128 | });
129 | console.log('menus', JSON.stringify(menus));
130 | }
131 | },
132 | };
133 | export default Model;
134 |
--------------------------------------------------------------------------------
/src/models/role.js:
--------------------------------------------------------------------------------
1 | import { create, get, query, remove, update } from '@/services/role';
2 |
3 | const Model = {
4 | namespace: 'role',
5 | state: {
6 | data: {
7 | list: [],
8 | pagination: {},
9 | },
10 | treeData: [],
11 | },
12 | effects: {
13 | *fetch({ payload, success }, { call, put }) {
14 | const { response, data } = yield call(query, payload);
15 | if (response.status === 200) {
16 | yield put({
17 | type: 'save',
18 | payload: data,
19 | });
20 | if (success) success(data.list);
21 | }
22 | },
23 |
24 | *get({ payload, success }, { call }) {
25 | const { response, data } = yield call(get, payload);
26 | if (response.status === 200) {
27 | if (success) success(data);
28 | }
29 | },
30 |
31 | *add({ payload, success }, { call, put }) {
32 | const { response } = yield call(create, payload);
33 | if (response.status === 200) {
34 | yield put({ type: 'global/requestSuccess', payload: {
35 | id: 'role(s)', action: 'create',
36 | }});
37 | if (success) success();
38 | }
39 | },
40 |
41 | *remove({ payload, success }, { call, put }) {
42 | const { response } = yield call(remove, payload);
43 | if (response.status === 200) {
44 | yield put({ type: 'global/requestSuccess', payload: {
45 | id: 'role(s)', action: 'delete',
46 | }});
47 | if (success) success();
48 | }
49 | },
50 |
51 | *update({ payload, success }, { call, put }) {
52 | const { response } = yield call(update, payload);
53 | if (response.status === 200) {
54 | yield put({ type: 'global/requestSuccess', payload: {
55 | id: 'role(s)', action: 'update',
56 | }});
57 | if (success) success();
58 | }
59 | },
60 | },
61 | reducers: {
62 | updateData(state, { payload }) {
63 | const { data } = state;
64 | data.list = data.list.map(item => {
65 | if (item.record_id === payload.record_id) {
66 | return payload;
67 | }
68 | return item;
69 | });
70 | return {
71 | ...state,
72 | data
73 | };
74 | },
75 | save(state, { payload }) {
76 | return {
77 | ...state,
78 | data: payload,
79 | };
80 | },
81 | },
82 | };
83 | export default Model;
84 |
--------------------------------------------------------------------------------
/src/models/setting.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '../../config/defaultSettings';
2 |
3 | const updateColorWeak = colorWeak => {
4 | const root = document.getElementById('root');
5 |
6 | if (root) {
7 | root.className = colorWeak ? 'colorWeak' : '';
8 | }
9 | };
10 |
11 | const SettingModel = {
12 | namespace: 'settings',
13 | state: defaultSettings,
14 | reducers: {
15 | changeSetting(state = defaultSettings, { payload }) {
16 | const { colorWeak, contentWidth } = payload;
17 |
18 | if (state.contentWidth !== contentWidth && window.dispatchEvent) {
19 | window.dispatchEvent(new Event('resize'));
20 | }
21 |
22 | updateColorWeak(!!colorWeak);
23 | return { ...state, ...payload };
24 | },
25 | },
26 | };
27 | export default SettingModel;
28 |
--------------------------------------------------------------------------------
/src/models/user.js:
--------------------------------------------------------------------------------
1 | import { query, disable, enable, create, remove, update, get } from '@/services/user';
2 | import md5 from 'md5';
3 |
4 | const Model = {
5 | namespace: 'user',
6 | state: {
7 | data: {
8 | list: [],
9 | pagination: {},
10 | },
11 | },
12 | effects: {
13 | *fetch({ payload, success }, { call, put }) {
14 | const { response, data } = yield call(query, payload);
15 | if (response.status === 200) {
16 | yield put({
17 | type: 'save',
18 | payload: data,
19 | });
20 | if (success) success();
21 | }
22 | },
23 |
24 | *get({ payload, success }, { call }) {
25 | const { response, data } = yield call(get, payload);
26 | if (response.status === 200) {
27 | if (success) success(data);
28 | }
29 | },
30 |
31 | *add({ payload, success }, { call, put }) {
32 | payload.password = md5(payload.password);
33 | const { response } = yield call(create, payload);
34 | if (response.status === 200) {
35 | yield put({ type: 'global/requestSuccess', payload: {
36 | id: 'user(s)', action: 'create',
37 | }});
38 | if (success) success();
39 | }
40 | },
41 |
42 | *remove({ payload, success }, { call, put }) {
43 | const { response } = yield call(remove, payload);
44 | if (response.status === 200) {
45 | yield put({ type: 'global/requestSuccess', payload: {
46 | id: 'user(s)', action: 'delete',
47 | }});
48 | if (success) success();
49 | }
50 | },
51 |
52 | *update({ payload, success }, { call, put }) {
53 | if (payload.password) {
54 | payload.password = md5(payload.password);
55 | }
56 | const { response } = yield call(update, payload);
57 | if (response.status === 200) {
58 | yield put({ type: 'global/requestSuccess', payload: {
59 | id: 'user(s)', action: 'update',
60 | }});
61 | if (success) success();
62 | }
63 | },
64 |
65 | *enable({ payload, callback }, { call, put }) {
66 | const { response } = yield call(enable, payload);
67 | if (response.status === 200) {
68 | yield put({ type: 'global/requestSuccess', payload: {
69 | id: 'user(s)', action: 'enable',
70 | }});
71 | yield put({
72 | type: 'updateData',
73 | payload: payload,
74 | });
75 | }
76 | if (callback) callback();
77 | },
78 |
79 | *disable({ payload, callback }, { call, put }) {
80 | const { response } = yield call(disable, payload);
81 | if (response.status === 200) {
82 | yield put({ type: 'global/requestSuccess', payload: {
83 | id: 'user(s)', action: 'disable',
84 | }});
85 | yield put({
86 | type: 'updateData',
87 | payload: payload,
88 | });
89 | }
90 | if (callback) callback();
91 | },
92 | },
93 | reducers: {
94 | updateData(state, { payload }) {
95 | state.data.list = state.data.list.map(item => {
96 | if (item.record_id === payload.record_id) {
97 | return payload;
98 | }
99 | return item;
100 | });
101 | return state;
102 | },
103 |
104 | save(state, { payload }) {
105 | return { ...state, data: payload };
106 | },
107 |
108 | changeNotifyCount(state, { payload }) {
109 | return { ...state };
110 | },
111 | },
112 | };
113 | export default Model;
114 |
--------------------------------------------------------------------------------
/src/pages/404.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Result } from 'antd';
2 | import React from 'react';
3 | import router from 'umi/router'; // 这里应该使用 antd 的 404 result 组件,
4 | // 但是还没发布,先来个简单的。
5 |
6 | const NoFoundPage = () => (
7 | router.push('/')}>
13 | Back Home
14 |
15 | }
16 | >
17 | );
18 |
19 | export default NoFoundPage;
20 |
--------------------------------------------------------------------------------
/src/pages/Authorized.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Redirect from 'umi/redirect';
3 | import { connect } from 'dva';
4 | import pathToRegexp from 'path-to-regexp';
5 | import Authorized from '@/utils/Authorized';
6 |
7 | const getRouteAuthority = (path, routeData) => {
8 | let authorities;
9 | routeData.forEach(route => {
10 | if (route.authority) {
11 | authorities = route.authority;
12 | } // match prefix
13 |
14 | if (pathToRegexp(`${route.path}(.*)`).test(path)) {
15 | // exact match
16 | if (route.path === path) {
17 | authorities = route.authority || authorities;
18 | } // get children authority recursively
19 |
20 | if (route.routes) {
21 | authorities = getRouteAuthority(path, route.routes) || authorities;
22 | }
23 | }
24 | });
25 | return authorities;
26 | };
27 |
28 | const AuthComponent = ({
29 | children,
30 | route = {
31 | routes: [],
32 | },
33 | location = {
34 | pathname: '',
35 | },
36 | global,
37 | }) => {
38 | const { currentUser } = global;
39 | const { routes = [] } = route;
40 | const isLogin = currentUser && currentUser.user_name;
41 | return (
42 | : }
45 | >
46 | {children}
47 |
48 | );
49 | };
50 |
51 | export default connect(({ global }) => ({
52 | global,
53 | }))(AuthComponent);
54 |
--------------------------------------------------------------------------------
/src/pages/Welcome.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PageHeaderWrapper } from '@ant-design/pro-layout';
3 | import { FormattedMessage, formatMessage } from 'umi-plugin-react/locale';
4 | import { Card, Typography, Alert } from 'antd';
5 | import styles from './Welcome.less';
6 |
7 | const CodePreview = ({ children }) => (
8 |
9 |
10 | {children}
11 |
12 |
13 | );
14 |
15 | export default () => (
16 |
17 |
18 |
30 |
31 |
32 |
36 |
37 |
38 |
39 | https://github.com/gin-admin/gin-admin
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 | go get -v github.com/LyricTian/gin-admin-cli && gin-admin-cli new -m -d ~/go/src/gin-admin -p gin-admin
51 |
52 |
53 |
54 | );
55 |
--------------------------------------------------------------------------------
/src/pages/Welcome.less:
--------------------------------------------------------------------------------
1 | @import '~antd/lib/style/themes/default.less';
2 |
3 | .pre {
4 | margin: 12px 0;
5 | padding: 12px 20px;
6 | background: @input-bg;
7 | box-shadow: @card-shadow;
8 | }
9 |
--------------------------------------------------------------------------------
/src/pages/system/menu/components/MenuAction/AddDialog.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Modal, Form, Row, Col, Checkbox, Radio, Button } from 'antd';
3 | import { FormattedMessage, formatMessage } from 'umi-plugin-react/locale';
4 |
5 | const basicTpl = ['query', 'create', 'update', 'delete'];
6 | const advancedTpl = ['query', 'create', 'update', 'delete', 'enable', 'disable'];
7 |
8 | @Form.create()
9 | class AddDialog extends PureComponent {
10 | handleCancel = () => {
11 | const { onCancel } = this.props;
12 | if (onCancel) onCancel();
13 | };
14 |
15 | handleOK = () => {
16 | const { form, onSubmit } = this.props;
17 | form.validateFieldsAndScroll((err, values) => {
18 | if (!err) {
19 | const formData = { ...values };
20 | onSubmit(formData);
21 | }
22 | });
23 | };
24 |
25 | render() {
26 | const {
27 | visible,
28 | form: { getFieldDecorator, setFieldsValue },
29 | } = this.props;
30 | const formItemLayout = {
31 | labelCol: {
32 | xs: { span: 24 },
33 | sm: { span: 6 },
34 | },
35 | wrapperCol: {
36 | xs: { span: 24 },
37 | sm: { span: 16 },
38 | },
39 | };
40 |
41 | return (
42 |
58 |
62 |
63 |
64 |
65 |
66 | {getFieldDecorator('type', {
67 | initialValue: 1,
68 | rules: [{ required: true }]
69 | })(
70 | setFieldsValue({ actions: e.target.value === 1 ? basicTpl : advancedTpl })
71 | }>
72 |
73 |
74 |
75 |
76 |
77 |
78 | )}
79 |
80 |
81 |
86 |
87 |
88 |
89 |
90 | {getFieldDecorator('actions', {
91 | initialValue: basicTpl,
92 | rules: [{ required: true }]
93 | })(
94 |
95 | Query
96 | Create
97 | Update
98 | Delete
99 | Enable
100 | Disable
101 |
102 | )}
103 |
104 |
105 |
106 |
107 |
108 | );
109 | }
110 | }
111 |
112 | export default AddDialog;
113 |
--------------------------------------------------------------------------------
/src/pages/system/menu/components/MenuAction/EditableCell.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Input, Form } from 'antd';
3 | import { PureComponent } from 'react';
4 | import { formatMessage } from 'umi-plugin-react/locale';
5 |
6 |
7 | const EditableContext = React.createContext();
8 |
9 | const EditableRow = ({ form, index, ...props }) => (
10 |
11 |
12 |
13 | );
14 |
15 | export const EditableFormRow = Form.create()(EditableRow);
16 |
17 | export class EditableCell extends PureComponent {
18 | save = e => {
19 | const { record, handleSave } = this.props;
20 | this.form.validateFields((error, values) => {
21 | if (error && error[e.currentTarget.id]) {
22 | return;
23 | }
24 | handleSave({ ...record, ...values });
25 | });
26 | };
27 |
28 | renderCell = form => {
29 | this.form = form;
30 | const { dataIndex, record, title } = this.props;
31 | return
32 | {form.getFieldDecorator(dataIndex, {
33 | rules: [
34 | {
35 | required: true,
36 | message: formatMessage({ id: 'component.placeholder.content' }, {
37 | content: title,
38 | }),
39 | },
40 | ],
41 | initialValue: record[dataIndex],
42 | })( (this.input = node)} onPressEnter={this.save} onBlur={this.save} />)}
43 |
44 | };
45 |
46 | render() {
47 | const {
48 | editable,
49 | dataIndex,
50 | title,
51 | record,
52 | index,
53 | handleSave,
54 | children,
55 | ...restProps
56 | } = this.props;
57 | return (
58 |
59 | {editable ? (
60 | {this.renderCell}
61 | ) : (
62 | children
63 | )}
64 | |
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/pages/system/menu/components/MenuResource/EditableCell.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Input, Form, Select } from 'antd';
3 | import { PureComponent } from 'react';
4 | import { formatMessage } from 'umi-plugin-react/locale';
5 |
6 | const EditableContext = React.createContext();
7 |
8 | const EditableRow = ({ form, index, ...props }) => (
9 |
10 |
11 |
12 | );
13 |
14 | export const EditableFormRow = Form.create()(EditableRow);
15 |
16 | export class EditableCell extends PureComponent {
17 | save = e => {
18 | const { record, handleSave } = this.props;
19 | this.form.validateFields((error, values) => {
20 | if (error && error[e.currentTarget.id]) {
21 | return;
22 | }
23 | handleSave({ ...record, ...values });
24 | });
25 | };
26 |
27 | renderCell = form => {
28 | this.form = form;
29 | const { dataIndex, record, title } = this.props;
30 |
31 | if (dataIndex === 'method') {
32 | return (
33 |
34 | {this.form.getFieldDecorator(dataIndex, {
35 | rules: [
36 | {
37 | required: true,
38 | message: formatMessage({ id: 'component.placeholder.select.content' }, {
39 | content: title,
40 | }),
41 | },
42 | ],
43 | initialValue: record[dataIndex],
44 | })(
45 |
59 | )}
60 |
61 | );
62 | }
63 | return
64 | {form.getFieldDecorator(dataIndex, {
65 | rules: [
66 | {
67 | required: true,
68 | message: formatMessage({ id: 'component.placeholder.content' }, {
69 | content: title,
70 | }),
71 | },
72 | ],
73 | initialValue: record[dataIndex],
74 | })( (this.input = node)} onPressEnter={this.save} onBlur={this.save} />)}
75 |
76 | };
77 |
78 | render() {
79 | const {
80 | editable,
81 | dataIndex,
82 | title,
83 | record,
84 | index,
85 | handleSave,
86 | children,
87 | ...restProps
88 | } = this.props;
89 | return (
90 |
91 | {editable ? (
92 | {this.renderCell}
93 | ) : (
94 | children
95 | )}
96 | |
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/pages/system/menu/components/StandardTable/index.jsx:
--------------------------------------------------------------------------------
1 | import { Table } from 'antd';
2 | import React, { Component } from 'react';
3 | import styles from './index.less';
4 | import { formatMessage } from 'umi-plugin-react/locale';
5 |
6 | class StandardTable extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | selectedRowKeys: [],
11 | };
12 | }
13 |
14 | handleRowSelectChange = (selectedRowKeys, selectedRows) => {
15 | const currySelectedRowKeys = selectedRowKeys;
16 | const { onSelectRow } = this.props;
17 | if (onSelectRow) {
18 | onSelectRow(selectedRows);
19 | }
20 |
21 | this.setState({
22 | selectedRowKeys: currySelectedRowKeys,
23 | });
24 | };
25 |
26 | handleTableChange = (pagination, filters, sorter, ...rest) => {
27 | const { onChange } = this.props;
28 | if (onChange) {
29 | onChange(pagination, filters, sorter, ...rest);
30 | }
31 | };
32 |
33 | render() {
34 | const { selectedRowKeys } = this.state;
35 | const { selectedRows, data, rowKey, ...rest } = this.props;
36 | const { list = [], pagination = false } = data || {};
37 | const paginationProps = pagination ? {
38 | showSizeChanger: true,
39 | showQuickJumper: true,
40 | ...pagination,
41 | showTotal: total => formatMessage({
42 | id: 'component.searchList.result.total'
43 | }, { total }),
44 | } : false;
45 | const rowSelection = {
46 | selectedRowKeys: selectedRows && selectedRows.length === 0 ? [] : selectedRowKeys,
47 | onChange: this.handleRowSelectChange,
48 | getCheckboxProps: record => ({
49 | disabled: record.disabled,
50 | }),
51 | };
52 | return (
53 |
63 | );
64 | }
65 | }
66 |
67 | export default StandardTable;
68 |
--------------------------------------------------------------------------------
/src/pages/system/menu/components/StandardTable/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .standardTable {
4 | :global {
5 | .ant-table-pagination {
6 | margin-top: 24px;
7 | }
8 | }
9 |
10 | .tableAlert {
11 | margin-bottom: 16px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/system/menu/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface TableListItem {
2 | user_name?: string;
3 | real_name: string;
4 | email: string;
5 | phone: string;
6 | status: number;
7 | created_at: Date;
8 | }
9 |
10 | export interface TableListPagination {
11 | total: number;
12 | pageSize: number;
13 | page: number;
14 | }
15 |
16 | export interface TableListData {
17 | list: TableListItem[];
18 | pagination: Partial;
19 | }
20 |
21 | export interface TableListParams {
22 | sorter: string;
23 | status: string;
24 | name: string;
25 | pageSize: number;
26 | page: number;
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/system/menu/style.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 | @import './utils/utils.less';
3 |
4 | .tableList {
5 | .tableListOperator {
6 | margin-bottom: 16px;
7 | button {
8 | margin-right: 8px;
9 | }
10 | }
11 | }
12 |
13 | .tableListForm {
14 | :global {
15 | .ant-form-item {
16 | display: flex;
17 | margin-right: 0;
18 | margin-bottom: 24px;
19 | > .ant-form-item-label {
20 | width: auto;
21 | padding-right: 8px;
22 | line-height: 32px;
23 | }
24 | .ant-form-item-control {
25 | line-height: 32px;
26 | }
27 | }
28 | .ant-form-item-control-wrapper {
29 | flex: 1;
30 | }
31 | }
32 | .submitButtons {
33 | display: block;
34 | margin-bottom: 24px;
35 | white-space: nowrap;
36 | }
37 | }
38 |
39 | @media screen and (max-width: @screen-lg) {
40 | .tableListForm :global(.ant-form-item) {
41 | margin-right: 24px;
42 | }
43 | }
44 |
45 | @media screen and (max-width: @screen-md) {
46 | .tableListForm :global(.ant-form-item) {
47 | margin-right: 8px;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/pages/system/menu/utils/utils.less:
--------------------------------------------------------------------------------
1 | .textOverflow() {
2 | overflow: hidden;
3 | white-space: nowrap;
4 | text-overflow: ellipsis;
5 | word-break: break-all;
6 | }
7 |
8 | .textOverflowMulti(@line: 3, @bg: #fff) {
9 | position: relative;
10 | max-height: @line * 1.5em;
11 | margin-right: -1em;
12 | padding-right: 1em;
13 | overflow: hidden;
14 | line-height: 1.5em;
15 | text-align: justify;
16 | &::before {
17 | position: absolute;
18 | right: 14px;
19 | bottom: 0;
20 | padding: 0 1px;
21 | background: @bg;
22 | content: '...';
23 | }
24 | &::after {
25 | position: absolute;
26 | right: 14px;
27 | width: 1em;
28 | height: 1em;
29 | margin-top: 0.2em;
30 | background: white;
31 | content: '';
32 | }
33 | }
34 |
35 | // mixins for clearfix
36 | // ------------------------
37 | .clearfix() {
38 | zoom: 1;
39 | &::before,
40 | &::after {
41 | display: table;
42 | content: ' ';
43 | }
44 | &::after {
45 | clear: both;
46 | height: 0;
47 | font-size: 0;
48 | visibility: hidden;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/system/role/components/RoleMenu/EditableCell.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Checkbox, Row, Col } from 'antd';
3 |
4 | export class EditableCell extends PureComponent {
5 | findItem = () => {
6 | const { data, record } = this.props;
7 | for (let i = 0; i < data.length; i += 1) {
8 | if (data[i].menu_id === record.record_id) {
9 | return data[i];
10 | }
11 | }
12 | return null;
13 | };
14 |
15 | handleChange = values => {
16 | const { record, dataIndex, handleSave } = this.props;
17 | handleSave(record, dataIndex, values);
18 | };
19 |
20 | renderAction = () => {
21 | const { record } = this.props;
22 | if (!record.actions || record.actions.length === 0) {
23 | return null;
24 | }
25 |
26 | const item = this.findItem();
27 | return (
28 |
33 |
34 | {record.actions.map(v => (
35 |
36 | {v.name}
37 |
38 | ))}
39 |
40 |
41 | );
42 | };
43 |
44 | renderResource = () => {
45 | const { record } = this.props;
46 | if (!record.resources || record.resources.length === 0) {
47 | return null;
48 | }
49 |
50 | const item = this.findItem();
51 | return (
52 |
57 |
58 | {record.resources.map(v => (
59 |
60 | {v.name}
61 |
62 | ))}
63 |
64 |
65 | );
66 | };
67 |
68 | render() {
69 | const { dataIndex, record, menuData, handleSave, ...restProps } = this.props;
70 | return (
71 |
72 | {dataIndex === 'actions' && this.renderAction()}
73 | {dataIndex === 'resources' && this.renderResource()}
74 | {!(dataIndex === 'actions' || dataIndex === 'resources') && restProps.children}
75 | |
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/pages/system/role/components/StandardTable/index.jsx:
--------------------------------------------------------------------------------
1 | import { Table } from 'antd';
2 | import React, { Component } from 'react';
3 | import styles from './index.less';
4 | import { formatMessage } from 'umi-plugin-react/locale';
5 |
6 | class StandardTable extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | selectedRowKeys: [],
11 | };
12 | }
13 |
14 | handleRowSelectChange = (selectedRowKeys, selectedRows) => {
15 | const currySelectedRowKeys = selectedRowKeys;
16 | const { onSelectRow } = this.props;
17 | if (onSelectRow) {
18 | onSelectRow(selectedRows);
19 | }
20 | this.setState({
21 | selectedRowKeys: currySelectedRowKeys,
22 | });
23 | };
24 |
25 | handleTableChange = (pagination, filters, sorter, ...rest) => {
26 | const { onChange } = this.props;
27 |
28 | if (onChange) {
29 | onChange(pagination, filters, sorter, ...rest);
30 | }
31 | };
32 |
33 | render() {
34 | const { selectedRowKeys } = this.state;
35 | const { selectedRows, data, rowKey, ...rest } = this.props;
36 | const { list = [], pagination = false } = data || {};
37 | const paginationProps = pagination ? {
38 | showSizeChanger: true,
39 | showQuickJumper: true,
40 | ...pagination,
41 | showTotal: total => formatMessage({
42 | id: 'component.searchList.result.total'
43 | }, { total }),
44 | } : false;
45 | const rowSelection = {
46 | selectedRowKeys: selectedRows && selectedRows.length === 0 ? [] : selectedRowKeys,
47 | onChange: this.handleRowSelectChange,
48 | getCheckboxProps: record => ({
49 | disabled: record.disabled,
50 | }),
51 | };
52 | return (
53 |
63 | );
64 | }
65 | }
66 |
67 | export default StandardTable;
68 |
--------------------------------------------------------------------------------
/src/pages/system/role/components/StandardTable/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .standardTable {
4 | :global {
5 | .ant-table-pagination {
6 | margin-top: 24px;
7 | }
8 | }
9 |
10 | .tableAlert {
11 | margin-bottom: 16px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/system/role/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface TableListItem {
2 | user_name?: string;
3 | real_name: string;
4 | email: string;
5 | phone: string;
6 | status: number;
7 | created_at: Date;
8 | }
9 |
10 | export interface TableListPagination {
11 | total: number;
12 | pageSize: number;
13 | page: number;
14 | }
15 |
16 | export interface TableListData {
17 | list: TableListItem[];
18 | pagination: Partial;
19 | }
20 |
21 | export interface TableListParams {
22 | sorter: string;
23 | status: string;
24 | name: string;
25 | pageSize: number;
26 | page: number;
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/system/role/style.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 | @import './utils/utils.less';
3 |
4 | .tableList {
5 | .tableListOperator {
6 | margin-bottom: 16px;
7 | button {
8 | margin-right: 8px;
9 | }
10 | }
11 | }
12 |
13 | .tableListForm {
14 | :global {
15 | .ant-form-item {
16 | display: flex;
17 | margin-right: 0;
18 | margin-bottom: 24px;
19 | > .ant-form-item-label {
20 | width: auto;
21 | padding-right: 8px;
22 | line-height: 32px;
23 | }
24 | .ant-form-item-control {
25 | line-height: 32px;
26 | }
27 | }
28 | .ant-form-item-control-wrapper {
29 | flex: 1;
30 | }
31 | }
32 | .submitButtons {
33 | display: block;
34 | margin-bottom: 24px;
35 | white-space: nowrap;
36 | }
37 | }
38 |
39 | @media screen and (max-width: @screen-lg) {
40 | .tableListForm :global(.ant-form-item) {
41 | margin-right: 24px;
42 | }
43 | }
44 |
45 | @media screen and (max-width: @screen-md) {
46 | .tableListForm :global(.ant-form-item) {
47 | margin-right: 8px;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/pages/system/role/utils/utils.less:
--------------------------------------------------------------------------------
1 | .textOverflow() {
2 | overflow: hidden;
3 | white-space: nowrap;
4 | text-overflow: ellipsis;
5 | word-break: break-all;
6 | }
7 |
8 | .textOverflowMulti(@line: 3, @bg: #fff) {
9 | position: relative;
10 | max-height: @line * 1.5em;
11 | margin-right: -1em;
12 | padding-right: 1em;
13 | overflow: hidden;
14 | line-height: 1.5em;
15 | text-align: justify;
16 | &::before {
17 | position: absolute;
18 | right: 14px;
19 | bottom: 0;
20 | padding: 0 1px;
21 | background: @bg;
22 | content: '...';
23 | }
24 | &::after {
25 | position: absolute;
26 | right: 14px;
27 | width: 1em;
28 | height: 1em;
29 | margin-top: 0.2em;
30 | background: white;
31 | content: '';
32 | }
33 | }
34 |
35 | // mixins for clearfix
36 | // ------------------------
37 | .clearfix() {
38 | zoom: 1;
39 | &::before,
40 | &::after {
41 | display: table;
42 | content: ' ';
43 | }
44 | &::after {
45 | clear: both;
46 | height: 0;
47 | font-size: 0;
48 | visibility: hidden;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/user/list/components/RoleSelect.jsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { Select } from 'antd';
3 | import { connect } from 'dva';
4 | import { formatMessage } from 'umi-plugin-react/locale';
5 |
6 | function parseValue(value) {
7 | if (!value) {
8 | return [];
9 | }
10 | return value.map(v => v.role_id);
11 | }
12 |
13 | @connect(({ role, loading }) => ({
14 | role,
15 | loading: loading.models.role,
16 | }))
17 | export default class RoleSelect extends PureComponent {
18 | constructor(props) {
19 | super(props);
20 |
21 | this.state = {
22 | value: parseValue(props.value),
23 | data: [],
24 | };
25 | }
26 |
27 | componentDidMount() {
28 | const { dispatch } = this.props;
29 | dispatch({
30 | type: 'role/fetch',
31 | payload: {
32 | q: 'select',
33 | },
34 | success: data => {
35 | this.setState({ data });
36 | }
37 | });
38 | }
39 |
40 | static getDerivedStateFromProps(nextProps, state) {
41 | if ('value' in nextProps) {
42 | return { ...state, value: parseValue(nextProps.value) };
43 | }
44 | return state;
45 | }
46 |
47 | handleChange = value => {
48 | this.setState({ value });
49 | this.triggerChange(value);
50 | };
51 |
52 | triggerChange = data => {
53 | const { onChange } = this.props;
54 | if (onChange) {
55 | const newData = data.map(v => ({ role_id: v }));
56 | onChange(newData);
57 | }
58 | };
59 |
60 | render() {
61 | const { value, data } = this.state;
62 |
63 | return (
64 |
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/pages/user/list/components/StandardTable/index.jsx:
--------------------------------------------------------------------------------
1 | import { Table } from 'antd';
2 | import React, { Component } from 'react';
3 | import styles from './index.less';
4 | import { formatMessage } from 'umi-plugin-react/locale';
5 |
6 | class StandardTable extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | selectedRowKeys: [],
11 | };
12 | }
13 |
14 | handleRowSelectChange = (selectedRowKeys, selectedRows) => {
15 | const currySelectedRowKeys = selectedRowKeys;
16 | const { onSelectRow } = this.props;
17 | if (onSelectRow) {
18 | onSelectRow(selectedRows);
19 | }
20 | this.setState({
21 | selectedRowKeys: currySelectedRowKeys,
22 | });
23 | };
24 |
25 | handleTableChange = (pagination, filters, sorter, ...rest) => {
26 | const { onChange } = this.props;
27 |
28 | if (onChange) {
29 | onChange(pagination, filters, sorter, ...rest);
30 | }
31 | };
32 |
33 | render() {
34 | const { selectedRowKeys } = this.state;
35 | const { selectedRows, data, rowKey, ...rest } = this.props;
36 | const { list = [], pagination = false } = data || {};
37 | const paginationProps = pagination ? {
38 | showSizeChanger: true,
39 | showQuickJumper: true,
40 | ...pagination,
41 | showTotal: total => formatMessage({
42 | id: 'component.searchList.result.total'
43 | }, { total }),
44 | } : false;
45 | const rowSelection = {
46 | selectedRowKeys: selectedRows && selectedRows.length === 0 ? [] : selectedRowKeys,
47 | onChange: this.handleRowSelectChange,
48 | getCheckboxProps: record => ({
49 | disabled: record.disabled,
50 | }),
51 | };
52 | return (
53 |
63 | );
64 | }
65 | }
66 |
67 | export default StandardTable;
68 |
--------------------------------------------------------------------------------
/src/pages/user/list/components/StandardTable/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .standardTable {
4 | :global {
5 | .ant-table-pagination {
6 | margin-top: 24px;
7 | }
8 | }
9 |
10 | .tableAlert {
11 | margin-bottom: 16px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/user/list/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface TableListItem {
2 | user_name?: string;
3 | real_name: string;
4 | email: string;
5 | phone: string;
6 | status: number;
7 | creator: string;
8 | created_at: Date;
9 | }
10 |
11 | export interface TableListPagination {
12 | total: number;
13 | pageSize: number;
14 | page: number;
15 | }
16 |
17 | export interface TableListData {
18 | list: TableListItem[];
19 | pagination: Partial;
20 | }
21 |
22 | export interface TableListParams {
23 | sorter: string;
24 | status: string;
25 | name: string;
26 | pageSize: number;
27 | page: number;
28 | }
29 |
--------------------------------------------------------------------------------
/src/pages/user/list/style.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 | @import './utils/utils.less';
3 |
4 | .tableList {
5 | .tableListOperator {
6 | margin-bottom: 16px;
7 | button {
8 | margin-right: 8px;
9 | }
10 | }
11 | }
12 |
13 | .tableListForm {
14 | :global {
15 | .ant-form-item {
16 | display: flex;
17 | margin-right: 0;
18 | margin-bottom: 24px;
19 | > .ant-form-item-label {
20 | min-width: 80px;
21 | padding-right: 8px;
22 | line-height: 32px;
23 | }
24 | .ant-form-item-control {
25 | line-height: 32px;
26 | }
27 | }
28 | .ant-form-item-control-wrapper {
29 | flex: 1;
30 | }
31 | }
32 | .submitButtons {
33 | display: block;
34 | margin-bottom: 24px;
35 | white-space: nowrap;
36 | }
37 | }
38 |
39 | @media screen and (max-width: @screen-lg) {
40 | .tableListForm :global(.ant-form-item) {
41 | margin-right: 24px;
42 | }
43 | }
44 |
45 | @media screen and (max-width: @screen-md) {
46 | .tableListForm :global(.ant-form-item) {
47 | margin-right: 8px;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/pages/user/list/utils/utils.less:
--------------------------------------------------------------------------------
1 | .textOverflow() {
2 | overflow: hidden;
3 | white-space: nowrap;
4 | text-overflow: ellipsis;
5 | word-break: break-all;
6 | }
7 |
8 | .textOverflowMulti(@line: 3, @bg: #fff) {
9 | position: relative;
10 | max-height: @line * 1.5em;
11 | margin-right: -1em;
12 | padding-right: 1em;
13 | overflow: hidden;
14 | line-height: 1.5em;
15 | text-align: justify;
16 | &::before {
17 | position: absolute;
18 | right: 14px;
19 | bottom: 0;
20 | padding: 0 1px;
21 | background: @bg;
22 | content: '...';
23 | }
24 | &::after {
25 | position: absolute;
26 | right: 14px;
27 | width: 1em;
28 | height: 1em;
29 | margin-top: 0.2em;
30 | background: white;
31 | content: '';
32 | }
33 | }
34 |
35 | // mixins for clearfix
36 | // ------------------------
37 | .clearfix() {
38 | zoom: 1;
39 | &::before,
40 | &::after {
41 | display: table;
42 | content: ' ';
43 | }
44 | &::after {
45 | clear: both;
46 | height: 0;
47 | font-size: 0;
48 | visibility: hidden;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/user/login/components/Login/LoginContext.jsx:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 | const LoginContext = createContext({});
3 | export default LoginContext;
4 |
--------------------------------------------------------------------------------
/src/pages/user/login/components/Login/LoginSubmit.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Form } from 'antd';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 | import styles from './index.less';
5 | const FormItem = Form.Item;
6 |
7 | const LoginSubmit = ({ className, ...rest }) => {
8 | const clsString = classNames(styles.submit, className);
9 | return (
10 |
11 |
12 |
13 | );
14 | };
15 |
16 | export default LoginSubmit;
17 |
--------------------------------------------------------------------------------
/src/pages/user/login/components/Login/LoginTab.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Tabs } from 'antd';
3 | import LoginContext from './LoginContext';
4 | const { TabPane } = Tabs;
5 |
6 | const generateId = (() => {
7 | let i = 0;
8 | return (prefix = '') => {
9 | i += 1;
10 | return `${prefix}${i}`;
11 | };
12 | })();
13 |
14 | class LoginTab extends Component {
15 | uniqueId = '';
16 |
17 | constructor(props) {
18 | super(props);
19 | this.uniqueId = generateId('login-tab-');
20 | }
21 |
22 | componentDidMount() {
23 | const { tabUtil } = this.props;
24 |
25 | if (tabUtil) {
26 | tabUtil.addTab(this.uniqueId);
27 | }
28 | }
29 |
30 | render() {
31 | const { children } = this.props;
32 | return {children};
33 | }
34 | }
35 |
36 | const WrapContext = props => (
37 |
38 | {value => }
39 |
40 | ); // 标志位 用来判断是不是自定义组件
41 |
42 | WrapContext.typeName = 'LoginTab';
43 | export default WrapContext;
44 |
--------------------------------------------------------------------------------
/src/pages/user/login/components/Login/index.jsx:
--------------------------------------------------------------------------------
1 | import { Form, Tabs } from 'antd';
2 | import React, { Component } from 'react';
3 | import classNames from 'classnames';
4 | import LoginContext from './LoginContext';
5 | import LoginItem from './LoginItem';
6 | import LoginSubmit from './LoginSubmit';
7 | import LoginTab from './LoginTab';
8 | import styles from './index.less';
9 |
10 | class Login extends Component {
11 | static Tab = LoginTab;
12 | static Submit = LoginSubmit;
13 | static defaultProps = {
14 | className: '',
15 | defaultActiveKey: '',
16 | onTabChange: () => {},
17 | onSubmit: () => {},
18 | };
19 |
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | type: props.defaultActiveKey,
24 | tabs: [],
25 | active: {},
26 | };
27 | }
28 |
29 | componentDidMount() {
30 | const { form, onCreate } = this.props;
31 |
32 | if (onCreate) {
33 | onCreate(form);
34 | }
35 | }
36 |
37 | onSwitch = type => {
38 | this.setState(
39 | {
40 | type,
41 | },
42 | () => {
43 | const { onTabChange } = this.props;
44 |
45 | if (onTabChange) {
46 | onTabChange(type);
47 | }
48 | },
49 | );
50 | };
51 | getContext = () => {
52 | const { form } = this.props;
53 | const { tabs = [] } = this.state;
54 | return {
55 | tabUtil: {
56 | addTab: id => {
57 | this.setState({
58 | tabs: [...tabs, id],
59 | });
60 | },
61 | removeTab: id => {
62 | this.setState({
63 | tabs: tabs.filter(currentId => currentId !== id),
64 | });
65 | },
66 | },
67 | form: { ...form },
68 | updateActive: activeItem => {
69 | const { type = '', active = {} } = this.state;
70 |
71 | if (active[type]) {
72 | active[type].push(activeItem);
73 | } else {
74 | active[type] = [activeItem];
75 | }
76 |
77 | this.setState({
78 | active,
79 | });
80 | },
81 | };
82 | };
83 | handleSubmit = e => {
84 | e.preventDefault();
85 | const { active = {}, type = '' } = this.state;
86 | const { form, onSubmit } = this.props;
87 | const activeFields = active[type] || [];
88 |
89 | if (form) {
90 | form.validateFields(
91 | activeFields,
92 | {
93 | force: true,
94 | },
95 | (err, values) => {
96 | if (onSubmit) {
97 | onSubmit(err, values);
98 | }
99 | },
100 | );
101 | }
102 | };
103 |
104 | render() {
105 | const { className, children } = this.props;
106 | const { type, tabs = [] } = this.state;
107 | const TabChildren = [];
108 | const otherChildren = [];
109 | React.Children.forEach(children, child => {
110 | if (!child) {
111 | return;
112 | }
113 |
114 | if (child.type.typeName === 'LoginTab') {
115 | TabChildren.push(child);
116 | } else {
117 | otherChildren.push(child);
118 | }
119 | });
120 | return (
121 |
122 |
123 |
140 |
141 |
142 | );
143 | }
144 | }
145 |
146 | Object.keys(LoginItem).forEach(item => {
147 | Login[item] = LoginItem[item];
148 | });
149 | export default Form.create()(Login);
150 |
--------------------------------------------------------------------------------
/src/pages/user/login/components/Login/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .login {
4 | :global {
5 | .ant-tabs .ant-tabs-bar {
6 | margin-bottom: 24px;
7 | text-align: center;
8 | border-bottom: 0;
9 | }
10 |
11 | .ant-form-item {
12 | margin: 0 2px 24px;
13 | }
14 | }
15 |
16 | .getCaptcha {
17 | display: block;
18 | width: 100%;
19 | }
20 |
21 | .icon {
22 | margin-left: 16px;
23 | color: rgba(0, 0, 0, 0.2);
24 | font-size: 24px;
25 | vertical-align: middle;
26 | cursor: pointer;
27 | transition: color 0.3s;
28 |
29 | &:hover {
30 | color: @primary-color;
31 | }
32 | }
33 |
34 | .other {
35 | margin-top: 24px;
36 | line-height: 22px;
37 | text-align: left;
38 |
39 | .register {
40 | float: right;
41 | }
42 | }
43 |
44 | .prefixIcon {
45 | color: @disabled-color;
46 | font-size: @font-size-base;
47 | }
48 |
49 | .submit {
50 | width: 100%;
51 | margin-top: 24px;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/pages/user/login/components/Login/map.jsx:
--------------------------------------------------------------------------------
1 | import { Icon } from 'antd';
2 | import React from 'react';
3 | import styles from './index.less';
4 | export default {
5 | UserName: {
6 | props: {
7 | size: 'large',
8 | id: 'userName',
9 | prefix: ,
10 | placeholder: 'admin',
11 | },
12 | rules: [
13 | {
14 | required: true,
15 | message: 'Please enter username!',
16 | },
17 | ],
18 | },
19 | Password: {
20 | props: {
21 | size: 'large',
22 | prefix: ,
23 | type: 'password',
24 | id: 'password',
25 | placeholder: '888888',
26 | },
27 | rules: [
28 | {
29 | required: true,
30 | message: 'Please enter password!',
31 | },
32 | ],
33 | },
34 | Mobile: {
35 | props: {
36 | size: 'large',
37 | prefix: ,
38 | placeholder: 'mobile number',
39 | },
40 | rules: [
41 | {
42 | required: true,
43 | message: 'Please enter mobile number!',
44 | },
45 | {
46 | pattern: /^1\d{10}$/,
47 | message: 'Wrong mobile number format!',
48 | },
49 | ],
50 | },
51 | Captcha: {
52 | props: {
53 | size: 'large',
54 | prefix: ,
55 | placeholder: 'captcha',
56 | },
57 | rules: [
58 | {
59 | required: true,
60 | message: 'Please enter Captcha!',
61 | },
62 | ],
63 | },
64 | SmsCaptcha: {
65 | props: {
66 | size: 'large',
67 | prefix: ,
68 | placeholder: 'captcha',
69 | },
70 | rules: [
71 | {
72 | required: true,
73 | message: 'Please enter Captcha!',
74 | },
75 | ],
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/src/pages/user/login/locales/en-US.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'user-login.login.userName': 'Username',
3 | 'user-login.login.password': 'Password',
4 | 'user-login.login.message-invalid-credentials':
5 | 'Invalid username or password(admin/ant.design)',
6 | 'user-login.login.message-invalid-verification-code': 'Invalid verification code',
7 | 'user-login.login.tab-login-credentials': 'Credentials',
8 | 'user-login.login.tab-login-mobile': 'Mobile number',
9 | 'user-login.login.remember-me': 'Remember me',
10 | 'user-login.login.forgot-password': 'Forgot your password?',
11 | 'user-login.login.sign-in-with': 'Sign in with',
12 | 'user-login.login.signup': 'Sign up',
13 | 'user-login.login.login': 'Login',
14 | 'user-login.register.register': 'Register',
15 | 'user-login.register.get-verification-code': 'Get code',
16 | 'user-login.register.sign-in': 'Already have an account?',
17 | 'user-login.register-result.msg': 'Account:registered at {email}',
18 | 'user-login.register-result.activation-email':
19 | 'The activation email has been sent to your email address and is valid for 24 hours. Please log in to the email in time and click on the link in the email to activate the account.',
20 | 'user-login.register-result.back-home': 'Back to home',
21 | 'user-login.register-result.view-mailbox': 'View mailbox',
22 | 'user-login.email.required': 'Please enter your email!',
23 | 'user-login.email.wrong-format': 'The email address is in the wrong format!',
24 | 'user-login.userName.required': 'Please enter your userName!',
25 | 'user-login.password.required': 'Please enter your password!',
26 | 'user-login.password.twice': 'The passwords entered twice do not match!',
27 | 'user-login.strength.msg':
28 | "Please enter at least 6 characters and don't use passwords that are easy to guess.",
29 | 'user-login.strength.strong': 'Strength: strong',
30 | 'user-login.strength.medium': 'Strength: medium',
31 | 'user-login.strength.short': 'Strength: too short',
32 | 'user-login.confirm-password.required': 'Please confirm your password!',
33 | 'user-login.phone-number.required': 'Please enter your phone number!',
34 | 'user-login.phone-number.wrong-format': 'Malformed phone number!',
35 | 'user-login.verification-code.required': 'Please enter the verification code!',
36 | 'user-login.title.required': 'Please enter a title',
37 | 'user-login.date.required': 'Please select the start and end date',
38 | 'user-login.goal.required': 'Please enter a description of the goal',
39 | 'user-login.standard.required': 'Please enter a metric',
40 | 'user-login.form.get-captcha': 'Get Captcha',
41 | 'user-login.captcha.second': 'sec',
42 | 'user-login.form.optional': ' (optional) ',
43 | 'user-login.form.submit': 'Submit',
44 | 'user-login.form.save': 'Save',
45 | 'user-login.email.placeholder': 'Email',
46 | 'user-login.password.placeholder': 'Password',
47 | 'user-login.confirm-password.placeholder': 'Confirm password',
48 | 'user-login.phone-number.placeholder': 'Phone number',
49 | 'user-login.verification-code.placeholder': 'Verification code',
50 | 'user-login.title.label': 'Title',
51 | 'user-login.title.placeholder': 'Give the target a name',
52 | 'user-login.date.label': 'Start and end date',
53 | 'user-login.placeholder.start': 'Start date',
54 | 'user-login.placeholder.end': 'End date',
55 | 'user-login.goal.label': 'Goal description',
56 | 'user-login.goal.placeholder': 'Please enter your work goals',
57 | 'user-login.standard.label': 'Metrics',
58 | 'user-login.standard.placeholder': 'Please enter a metric',
59 | 'user-login.client.label': 'Client',
60 | 'user-login.label.tooltip': 'Target service object',
61 | 'user-login.client.placeholder':
62 | 'Please describe your customer service, internal customers directly @ Name / job number',
63 | 'user-login.invites.label': 'Inviting critics',
64 | 'user-login.invites.placeholder':
65 | 'Please direct @ Name / job number, you can invite up to 5 people',
66 | 'user-login.weight.label': 'Weight',
67 | 'user-login.weight.placeholder': 'Please enter weight',
68 | 'user-login.public.label': 'Target disclosure',
69 | 'user-login.label.help': 'Customers and invitees are shared by default',
70 | 'user-login.radio.public': 'Public',
71 | 'user-login.radio.partially-public': 'Partially public',
72 | 'user-login.radio.private': 'Private',
73 | 'user-login.publicUsers.placeholder': 'Open to',
74 | 'user-login.option.A': 'Colleague A',
75 | 'user-login.option.B': 'Colleague B',
76 | 'user-login.option.C': 'Colleague C',
77 | 'user-login.navBar.lang': 'Languages',
78 | };
79 |
--------------------------------------------------------------------------------
/src/pages/user/login/locales/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'user-login.login.userName': '用户名',
3 | 'user-login.login.password': '密码',
4 | 'user-login.login.message-invalid-credentials': '账户或密码错误(admin/ant.design)',
5 | 'user-login.login.message-invalid-verification-code': '验证码错误',
6 | 'user-login.login.tab-login-credentials': '账户密码登录',
7 | 'user-login.login.tab-login-mobile': '手机号登录',
8 | 'user-login.login.remember-me': '自动登录',
9 | 'user-login.login.forgot-password': '忘记密码',
10 | 'user-login.login.sign-in-with': '其他登录方式',
11 | 'user-login.login.signup': '注册账户',
12 | 'user-login.login.login': '登录',
13 | 'user-login.register.register': '注册',
14 | 'user-login.register.get-verification-code': '获取验证码',
15 | 'user-login.register.sign-in': '使用已有账户登录',
16 | 'user-login.register-result.msg': '你的账户:{email} 注册成功',
17 | 'user-login.register-result.activation-email':
18 | '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。',
19 | 'user-login.register-result.back-home': '返回首页',
20 | 'user-login.register-result.view-mailbox': '查看邮箱',
21 | 'user-login.email.required': '请输入邮箱地址!',
22 | 'user-login.email.wrong-format': '邮箱地址格式错误!',
23 | 'user-login.userName.required': '请输入用户名!',
24 | 'user-login.password.required': '请输入密码!',
25 | 'user-login.password.twice': '两次输入的密码不匹配!',
26 | 'user-login.strength.msg': '请至少输入 6 个字符。请不要使用容易被猜到的密码。',
27 | 'user-login.strength.strong': '强度:强',
28 | 'user-login.strength.medium': '强度:中',
29 | 'user-login.strength.short': '强度:太短',
30 | 'user-login.confirm-password.required': '请确认密码!',
31 | 'user-login.phone-number.required': '请输入手机号!',
32 | 'user-login.phone-number.wrong-format': '手机号格式错误!',
33 | 'user-login.verification-code.required': '请输入验证码!',
34 | 'user-login.title.required': '请输入标题',
35 | 'user-login.date.required': '请选择起止日期',
36 | 'user-login.goal.required': '请输入目标描述',
37 | 'user-login.standard.required': '请输入衡量标准',
38 | 'user-login.form.get-captcha': '获取验证码',
39 | 'user-login.captcha.second': '秒',
40 | 'user-login.form.optional': '(选填)',
41 | 'user-login.form.submit': '提交',
42 | 'user-login.form.save': '保存',
43 | 'user-login.email.placeholder': '邮箱',
44 | 'user-login.password.placeholder': '至少6位密码,区分大小写',
45 | 'user-login.confirm-password.placeholder': '确认密码',
46 | 'user-login.phone-number.placeholder': '手机号',
47 | 'user-login.verification-code.placeholder': '验证码',
48 | 'user-login.title.label': '标题',
49 | 'user-login.title.placeholder': '给目标起个名字',
50 | 'user-login.date.label': '起止日期',
51 | 'user-login.placeholder.start': '开始日期',
52 | 'user-login.placeholder.end': '结束日期',
53 | 'user-login.goal.label': '目标描述',
54 | 'user-login.goal.placeholder': '请输入你的阶段性工作目标',
55 | 'user-login.standard.label': '衡量标准',
56 | 'user-login.standard.placeholder': '请输入衡量标准',
57 | 'user-login.client.label': '客户',
58 | 'user-login.label.tooltip': '目标的服务对象',
59 | 'user-login.client.placeholder': '请描述你服务的客户,内部客户直接 @姓名/工号',
60 | 'user-login.invites.label': '邀评人',
61 | 'user-login.invites.placeholder': '请直接 @姓名/工号,最多可邀请 5 人',
62 | 'user-login.weight.label': '权重',
63 | 'user-login.weight.placeholder': '请输入',
64 | 'user-login.public.label': '目标公开',
65 | 'user-login.label.help': '客户、邀评人默认被分享',
66 | 'user-login.radio.public': '公开',
67 | 'user-login.radio.partially-public': '部分公开',
68 | 'user-login.radio.private': '不公开',
69 | 'user-login.publicUsers.placeholder': '公开给',
70 | 'user-login.option.A': '同事甲',
71 | 'user-login.option.B': '同事乙',
72 | 'user-login.option.C': '同事丙',
73 | 'user-login.navBar.lang': '语言',
74 | };
75 |
--------------------------------------------------------------------------------
/src/pages/user/login/locales/zh-TW.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'user-login.login.userName': '賬戶',
3 | 'user-login.login.password': '密碼',
4 | 'user-login.login.message-invalid-credentials': '賬戶或密碼錯誤(admin/ant.design)',
5 | 'user-login.login.message-invalid-verification-code': '驗證碼錯誤',
6 | 'user-login.login.tab-login-credentials': '賬戶密碼登錄',
7 | 'user-login.login.tab-login-mobile': '手機號登錄',
8 | 'user-login.login.remember-me': '自動登錄',
9 | 'user-login.login.forgot-password': '忘記密碼',
10 | 'user-login.login.sign-in-with': '其他登錄方式',
11 | 'user-login.login.signup': '註冊賬戶',
12 | 'user-login.login.login': '登錄',
13 | 'user-login.register.register': '註冊',
14 | 'user-login.register.get-verification-code': '獲取驗證碼',
15 | 'user-login.register.sign-in': '使用已有賬戶登錄',
16 | 'user-login.register-result.msg': '妳的賬戶:{email} 註冊成功',
17 | 'user-login.register-result.activation-email':
18 | '激活郵件已發送到妳的郵箱中,郵件有效期為24小時。請及時登錄郵箱,點擊郵件中的鏈接激活帳戶。',
19 | 'user-login.register-result.back-home': '返回首頁',
20 | 'user-login.register-result.view-mailbox': '查看郵箱',
21 | 'user-login.email.required': '請輸入郵箱地址!',
22 | 'user-login.email.wrong-format': '郵箱地址格式錯誤!',
23 | 'user-login.userName.required': '請輸入賬戶!',
24 | 'user-login.password.required': '請輸入密碼!',
25 | 'user-login.password.twice': '兩次輸入的密碼不匹配!',
26 | 'user-login.strength.msg': '請至少輸入 6 個字符。請不要使用容易被猜到的密碼。',
27 | 'user-login.strength.strong': '強度:強',
28 | 'user-login.strength.medium': '強度:中',
29 | 'user-login.strength.short': '強度:太短',
30 | 'user-login.confirm-password.required': '請確認密碼!',
31 | 'user-login.phone-number.required': '請輸入手機號!',
32 | 'user-login.phone-number.wrong-format': '手機號格式錯誤!',
33 | 'user-login.verification-code.required': '請輸入驗證碼!',
34 | 'user-login.title.required': '請輸入標題',
35 | 'user-login.date.required': '請選擇起止日期',
36 | 'user-login.goal.required': '請輸入目標描述',
37 | 'user-login.standard.required': '請輸入衡量標淮',
38 | 'user-login.form.get-captcha': '獲取驗證碼',
39 | 'user-login.captcha.second': '秒',
40 | 'user-login.form.optional': '(選填)',
41 | 'user-login.form.submit': '提交',
42 | 'user-login.form.save': '保存',
43 | 'user-login.email.placeholder': '郵箱',
44 | 'user-login.password.placeholder': '至少6位密碼,區分大小寫',
45 | 'user-login.confirm-password.placeholder': '確認密碼',
46 | 'user-login.phone-number.placeholder': '手機號',
47 | 'user-login.verification-code.placeholder': '驗證碼',
48 | 'user-login.title.label': '標題',
49 | 'user-login.title.placeholder': '給目標起個名字',
50 | 'user-login.date.label': '起止日期',
51 | 'user-login.placeholder.start': '開始日期',
52 | 'user-login.placeholder.end': '結束日期',
53 | 'user-login.goal.label': '目標描述',
54 | 'user-login.goal.placeholder': '請輸入妳的階段性工作目標',
55 | 'user-login.standard.label': '衡量標淮',
56 | 'user-login.standard.placeholder': '請輸入衡量標淮',
57 | 'user-login.client.label': '客戶',
58 | 'user-login.label.tooltip': '目標的服務對象',
59 | 'user-login.client.placeholder': '請描述妳服務的客戶,內部客戶直接 @姓名/工號',
60 | 'user-login.invites.label': '邀評人',
61 | 'user-login.invites.placeholder': '請直接 @姓名/工號,最多可邀請 5 人',
62 | 'user-login.weight.label': '權重',
63 | 'user-login.weight.placeholder': '請輸入',
64 | 'user-login.public.label': '目標公開',
65 | 'user-login.label.help': '客戶、邀評人默認被分享',
66 | 'user-login.radio.public': '公開',
67 | 'user-login.radio.partially-public': '部分公開',
68 | 'user-login.radio.private': '不公開',
69 | 'user-login.publicUsers.placeholder': '公開給',
70 | 'user-login.option.A': '同事甲',
71 | 'user-login.option.B': '同事乙',
72 | 'user-login.option.C': '同事丙',
73 | 'user-login.navBar.lang': '語言',
74 | };
75 |
--------------------------------------------------------------------------------
/src/pages/user/login/style.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .main {
4 | width: 368px;
5 | margin: 0 auto;
6 | @media screen and (max-width: @screen-sm) {
7 | width: 95%;
8 | }
9 |
10 | .icon {
11 | margin-left: 16px;
12 | color: rgba(0, 0, 0, 0.2);
13 | font-size: 24px;
14 | vertical-align: middle;
15 | cursor: pointer;
16 | transition: color 0.3s;
17 |
18 | &:hover {
19 | color: @primary-color;
20 | }
21 | }
22 |
23 | .other {
24 | margin-top: 24px;
25 | line-height: 22px;
26 | text-align: left;
27 | display: none;
28 |
29 | .register {
30 | float: right;
31 | }
32 | }
33 |
34 | :global {
35 | .antd-pro-login-submit {
36 | width: 100%;
37 | margin-top: 24px;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable eslint-comments/disable-enable-pair */
2 | /* eslint-disable no-restricted-globals */
3 | /* eslint-disable no-underscore-dangle */
4 | /* globals workbox */
5 | workbox.core.setCacheNameDetails({
6 | prefix: 'antd-pro',
7 | suffix: 'v1',
8 | });
9 | // Control all opened tabs ASAP
10 | workbox.clientsClaim();
11 |
12 | /**
13 | * Use precaching list generated by workbox in build process.
14 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
15 | */
16 | workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
17 |
18 | /**
19 | * Register a navigation route.
20 | * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
21 | */
22 | workbox.routing.registerNavigationRoute('/index.html');
23 |
24 | /**
25 | * Use runtime cache:
26 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
27 | *
28 | * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
29 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
30 | */
31 |
32 | /**
33 | * Handle API requests
34 | */
35 | workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
36 |
37 | /**
38 | * Handle third party requests
39 | */
40 | workbox.routing.registerRoute(
41 | /^https:\/\/gw.alipayobjects.com\//,
42 | workbox.strategies.networkFirst(),
43 | );
44 | workbox.routing.registerRoute(
45 | /^https:\/\/cdnjs.cloudflare.com\//,
46 | workbox.strategies.networkFirst(),
47 | );
48 | workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
49 |
50 | /**
51 | * Response to client after skipping waiting with MessageChannel
52 | */
53 | addEventListener('message', event => {
54 | const replyPort = event.ports[0];
55 | const message = event.data;
56 | if (replyPort && message && message.type === 'skip-waiting') {
57 | event.waitUntil(
58 | self.skipWaiting().then(
59 | () =>
60 | replyPort.postMessage({
61 | error: null,
62 | }),
63 | error =>
64 | replyPort.postMessage({
65 | error,
66 | }),
67 | ),
68 | );
69 | }
70 | });
71 |
--------------------------------------------------------------------------------
/src/services/login.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request';
2 | import * as API from '@/api';
3 |
4 | export async function accountLogin(params) {
5 | return request(API.Public.Login.Base, {
6 | method: 'POST',
7 | data: params,
8 | });
9 | }
10 |
11 | export async function accountLogout() {
12 | return request(API.Public.Login.Exit, {
13 | method: 'POST',
14 | });
15 | }
16 |
17 | export async function menuTree() {
18 | return request(API.Public.Current.QueryUserMenuTree);
19 | }
20 |
21 | export async function current() {
22 | return request(API.Public.Current.GetUserInfo);
23 | }
24 |
25 | export async function updatePassword(params) {
26 | return request(API.Public.Current.UpdatePassword, {
27 | method: 'PUT',
28 | data: params,
29 | });
30 | }
31 |
32 | export async function getCaptcha() {
33 | return request(API.Public.Login.GetCaptcha);
34 | }
35 |
36 | export async function refreshCaptcha(params) {
37 | return request(API.Public.Login.ResCaptcha, {
38 | method: 'GET',
39 | data: {
40 | ...params,
41 | reload: 'yes',
42 | },
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/src/services/menu.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request';
2 | import * as API from '@/api';
3 |
4 | export async function query(params) {
5 | return request(API.Menu.Base, {
6 | params,
7 | });
8 | }
9 |
10 | export async function queryTree(params) {
11 | return request(API.Menu.Tree, {
12 | params,
13 | });
14 | }
15 |
16 | export async function get(params) {
17 | return request(`${API.Menu.Base}/${params.record_id}`);
18 | }
19 |
20 | export async function create(params) {
21 | return request(API.Menu.Base, {
22 | method: 'POST',
23 | data: params,
24 | });
25 | }
26 |
27 | export async function remove(params) {
28 | return request(`${API.Menu.Base}/${params.record_id}`, {
29 | method: 'DELETE',
30 | });
31 | }
32 |
33 | export async function update(params) {
34 | return request(`${API.Menu.Base}/${params.record_id}`, {
35 | method: 'PUT',
36 | data: params,
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/src/services/role.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request';
2 | import * as API from '@/api';
3 |
4 | export async function query(params) {
5 | return request(API.Role.Base, {
6 | params,
7 | });
8 | }
9 |
10 | export async function get(params) {
11 | return request(`${API.Role.Base}/${params.record_id}`);
12 | }
13 |
14 | export async function create(params) {
15 | return request(API.Role.Base, {
16 | method: 'POST',
17 | data: params,
18 | });
19 | }
20 |
21 | export async function remove(params) {
22 | return request(`${API.Role.Base}/${params.record_id}`, {
23 | method: 'DELETE',
24 | });
25 | }
26 |
27 | export async function update(params) {
28 | return request(`${API.Role.Base}/${params.record_id}`, {
29 | method: 'PUT',
30 | data: params,
31 | });
32 | }
33 |
34 | export async function disable(params) {
35 | return request(`${API.Role.Base}/${params.record_id}/disable`, {
36 | method: 'PATCH',
37 | });
38 | }
39 |
40 | export async function enable(params) {
41 | return request(`${API.Role.Base}/${params.record_id}/enable`, {
42 | method: 'PATCH',
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/src/services/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request';
2 | import * as API from '@/api';
3 |
4 | export async function query(params) {
5 | return request(API.User.Base, {
6 | params,
7 | });
8 | }
9 |
10 | export async function get(params) {
11 | return request(`${API.User.Base}/${params.record_id}`);
12 | }
13 |
14 | export async function create(params) {
15 | return request(API.User.Base, {
16 | method: 'POST',
17 | data: params,
18 | });
19 | }
20 |
21 | export async function remove(params) {
22 | return request(`${API.User.Base}/${params.record_id}`, {
23 | method: 'DELETE',
24 | });
25 | }
26 |
27 | export async function update(params) {
28 | return request(`${API.User.Base}/${params.record_id}`, {
29 | method: 'PUT',
30 | data: params,
31 | });
32 | }
33 |
34 | export async function disable(params) {
35 | return request(`${API.User.Base}/${params.record_id}/disable`, {
36 | method: 'PATCH',
37 | });
38 | }
39 |
40 | export async function enable(params) {
41 | return request(`${API.User.Base}/${params.record_id}/enable`, {
42 | method: 'PATCH',
43 | });
44 | }
45 |
46 | export async function queryNotices() {
47 | return request('/api/notices');
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/Authorized.js:
--------------------------------------------------------------------------------
1 | import RenderAuthorize from '@/components/Authorized';
2 | import { getAuthority } from './authority';
3 | /* eslint-disable eslint-comments/disable-enable-pair */
4 |
5 | /* eslint-disable import/no-mutable-exports */
6 |
7 | let Authorized = RenderAuthorize(getAuthority()); // Reload the rights component
8 |
9 | const reloadAuthorized = () => {
10 | Authorized = RenderAuthorize(getAuthority());
11 | };
12 |
13 | export { reloadAuthorized };
14 | export default Authorized;
15 |
--------------------------------------------------------------------------------
/src/utils/authority.js:
--------------------------------------------------------------------------------
1 | import { reloadAuthorized } from './Authorized'; // use localStorage to store the authority info, which might be sent from server in actual project.
2 |
3 | export function getAuthority(str) {
4 | const authorityString =
5 | typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str; // authorityString could be admin, "admin", ["admin"]
6 |
7 | let authority;
8 |
9 | try {
10 | if (authorityString) {
11 | authority = JSON.parse(authorityString);
12 | }
13 | } catch (e) {
14 | authority = authorityString;
15 | }
16 |
17 | if (typeof authority === 'string') {
18 | return [authority];
19 | } // preview.pro.ant.design only do not use in your production.
20 | // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
21 |
22 | if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
23 | return ['admin'];
24 | }
25 |
26 | return authority;
27 | }
28 | export function setAuthority(authority) {
29 | const proAuthority = typeof authority === 'string' ? [authority] : authority;
30 | localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); // auto reload
31 |
32 | reloadAuthorized();
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/authority.test.js:
--------------------------------------------------------------------------------
1 | import { getAuthority } from './authority';
2 | describe('getAuthority should be strong', () => {
3 | it('string', () => {
4 | expect(getAuthority('admin')).toEqual(['admin']);
5 | });
6 | it('array with double quotes', () => {
7 | expect(getAuthority('"admin"')).toEqual(['admin']);
8 | });
9 | it('array with single item', () => {
10 | expect(getAuthority('["admin"]')).toEqual(['admin']);
11 | });
12 | it('array with multiple items', () => {
13 | expect(getAuthority('["admin", "guest"]')).toEqual(['admin', 'guest']);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | /**
2 | * request 网络请求工具
3 | * 更详细的 api 文档: https://github.com/umijs/umi-request
4 | */
5 | import { extend } from 'umi-request';
6 | import { notification } from 'antd';
7 | // import moment from 'moment';
8 | import store from '@/utils/store';
9 |
10 | const codeMessage = {
11 | 200: '服务器成功返回请求的数据。',
12 | 201: '新建或修改数据成功。',
13 | 202: '一个请求已经进入后台排队(异步任务)。',
14 | 204: '删除数据成功。',
15 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
16 | 401: '用户没有权限(令牌、用户名、密码错误)。',
17 | 403: '用户得到授权,但是访问是被禁止的。',
18 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
19 | 406: '请求的格式不可得。',
20 | 410: '请求的资源被永久删除,且不会再得到的。',
21 | 422: '当创建一个对象时,发生一个验证错误。',
22 | 500: '服务器发生错误,请检查服务器。',
23 | 502: '网关错误。',
24 | 503: '服务不可用,服务器暂时过载或维护。',
25 | 504: '网关超时。',
26 | };
27 | /**
28 | * 异常处理程序
29 | */
30 |
31 | const errorHandler = error => {
32 | const { response, data } = error;
33 |
34 | if (data && data.error && data.error.message) {
35 | const { status, url } = response;
36 | notification.error({
37 | message: `请求错误 ${status}: ${url}`,
38 | description: data.error.message,
39 | });
40 | } else if (response && response.status) {
41 | const errorText = codeMessage[response.status] || response.statusText;
42 | const { status, url } = response;
43 |
44 | notification.error({
45 | message: `请求错误 ${status}: ${url}`,
46 | description: errorText,
47 | });
48 | } else if (!response) {
49 | notification.error({
50 | description: '您的网络发生异常,无法连接服务器',
51 | message: '网络异常',
52 | });
53 | }
54 |
55 | return {
56 | response,
57 | data,
58 | };
59 | };
60 | /**
61 | * 配置request请求时的默认参数
62 | */
63 |
64 | const request = extend({
65 | useCache: false,
66 | errorHandler,
67 | getResponse: true,
68 | // 默认错误处理
69 | // credentials: 'include', // 默认请求是否带上cookie
70 | });
71 |
72 | function getAccessToken() {
73 | const { token_type, access_token } = store.getAccessToken();
74 | if (!token_type || !access_token) {
75 | return '';
76 | }
77 |
78 | return `${token_type} ${access_token}`;
79 | }
80 |
81 | request.interceptors.request.use((url, options) => {
82 | const auth = getAccessToken();
83 | if (auth) {
84 | return {
85 | url,
86 | options: {
87 | ...options,
88 | interceptors: true,
89 | headers: {
90 | ...options.headers,
91 | Authorization: getAccessToken(),
92 | },
93 | params: {
94 | ...options.params,
95 | },
96 | },
97 | };
98 | } else {
99 | return { url, options };
100 | }
101 | });
102 | export default request;
103 |
--------------------------------------------------------------------------------
/src/utils/store.js:
--------------------------------------------------------------------------------
1 | const accessTokenKey = 'access_token';
2 |
3 | export default class store {
4 | static active = false;
5 | // 设定访问令牌
6 | static setAccessToken(token) {
7 | this.active = true;
8 | sessionStorage.setItem(accessTokenKey, JSON.stringify(token));
9 | }
10 |
11 | // 获取访问令牌
12 | static getAccessToken() {
13 | if (!this.active) return '';
14 | const token = sessionStorage.getItem(accessTokenKey);
15 | if (!token || token === '') {
16 | return null;
17 | }
18 | return JSON.parse(token);
19 | }
20 |
21 | // 清空访问令牌
22 | static clearAccessToken() {
23 | this.active = false;
24 | sessionStorage.removeItem(accessTokenKey);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/utils/utils.js:
--------------------------------------------------------------------------------
1 | import { parse } from 'querystring';
2 | import pathRegexp from 'path-to-regexp';
3 | /* eslint no-useless-escape:0 import/prefer-default-export:0 */
4 |
5 | const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
6 | export const isUrl = path => reg.test(path);
7 | export const isAntDesignPro = () => {
8 | if (ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
9 | return true;
10 | }
11 |
12 | return window.location.hostname === 'preview.pro.ant.design';
13 | }; // 给官方演示站点用,用于关闭真实开发环境不需要使用的特性
14 |
15 | export const isAntDesignProOrDev = () => {
16 | const { NODE_ENV } = process.env;
17 |
18 | if (NODE_ENV === 'development') {
19 | return true;
20 | }
21 |
22 | return isAntDesignPro();
23 | };
24 | export const getPageQuery = () => parse(window.location.href.split('?')[1]);
25 | /**
26 | * props.route.routes
27 | * @param router [{}]
28 | * @param pathname string
29 | */
30 |
31 | export const getAuthorityFromRouter = (router = [], pathname) => {
32 | const authority = router.find(({ path }) => path && pathRegexp(path).exec(pathname));
33 | if (authority) return authority;
34 | return undefined;
35 | };
36 |
--------------------------------------------------------------------------------
/src/utils/utils.less:
--------------------------------------------------------------------------------
1 | .textOverflow() {
2 | overflow: hidden;
3 | white-space: nowrap;
4 | text-overflow: ellipsis;
5 | word-break: break-all;
6 | }
7 |
8 | .textOverflowMulti(@line: 3, @bg: #fff) {
9 | position: relative;
10 | max-height: @line * 1.5em;
11 | margin-right: -1em;
12 | padding-right: 1em;
13 | overflow: hidden;
14 | line-height: 1.5em;
15 | text-align: justify;
16 | &::before {
17 | position: absolute;
18 | right: 14px;
19 | bottom: 0;
20 | padding: 0 1px;
21 | background: @bg;
22 | content: '...';
23 | }
24 | &::after {
25 | position: absolute;
26 | right: 14px;
27 | width: 1em;
28 | height: 1em;
29 | margin-top: 0.2em;
30 | background: white;
31 | content: '';
32 | }
33 | }
34 |
35 | // mixins for clearfix
36 | // ------------------------
37 | .clearfix() {
38 | zoom: 1;
39 | &::before,
40 | &::after {
41 | display: table;
42 | content: ' ';
43 | }
44 | &::after {
45 | clear: both;
46 | height: 0;
47 | font-size: 0;
48 | visibility: hidden;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/utils.test.js:
--------------------------------------------------------------------------------
1 | import { isUrl } from './utils';
2 | describe('isUrl tests', () => {
3 | it('should return false for invalid and corner case inputs', () => {
4 | expect(isUrl([])).toBeFalsy();
5 | expect(isUrl({})).toBeFalsy();
6 | expect(isUrl(false)).toBeFalsy();
7 | expect(isUrl(true)).toBeFalsy();
8 | expect(isUrl(NaN)).toBeFalsy();
9 | expect(isUrl(null)).toBeFalsy();
10 | expect(isUrl(undefined)).toBeFalsy();
11 | expect(isUrl('')).toBeFalsy();
12 | });
13 | it('should return false for invalid URLs', () => {
14 | expect(isUrl('foo')).toBeFalsy();
15 | expect(isUrl('bar')).toBeFalsy();
16 | expect(isUrl('bar/test')).toBeFalsy();
17 | expect(isUrl('http:/example.com/')).toBeFalsy();
18 | expect(isUrl('ttp://example.com/')).toBeFalsy();
19 | });
20 | it('should return true for valid URLs', () => {
21 | expect(isUrl('http://example.com/')).toBeTruthy();
22 | expect(isUrl('https://example.com/')).toBeTruthy();
23 | expect(isUrl('http://example.com/test/123')).toBeTruthy();
24 | expect(isUrl('https://example.com/test/123')).toBeTruthy();
25 | expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy();
26 | expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy();
27 | expect(isUrl('http://www.example.com/')).toBeTruthy();
28 | expect(isUrl('https://www.example.com/')).toBeTruthy();
29 | expect(isUrl('http://www.example.com/test/123')).toBeTruthy();
30 | expect(isUrl('https://www.example.com/test/123')).toBeTruthy();
31 | expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy();
32 | expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/run-tests.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable eslint-comments/disable-enable-pair */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | /* eslint-disable eslint-comments/no-unlimited-disable */
4 | const { spawn } = require('child_process');
5 | // eslint-disable-next-line import/no-extraneous-dependencies
6 | const { kill } = require('cross-port-killer');
7 |
8 | const env = Object.create(process.env);
9 | env.BROWSER = 'none';
10 | env.TEST = true;
11 | env.UMI_UI = 'none';
12 | env.PROGRESS = 'none';
13 | // flag to prevent multiple test
14 | let once = false;
15 |
16 | const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], {
17 | env,
18 | });
19 |
20 | startServer.stderr.on('data', data => {
21 | // eslint-disable-next-line
22 | console.log(data.toString());
23 | });
24 |
25 | startServer.on('exit', () => {
26 | kill(process.env.PORT || 8000);
27 | });
28 |
29 | console.log('Starting development server for e2e tests...');
30 | startServer.stdout.on('data', data => {
31 | console.log(data.toString());
32 | // hack code , wait umi
33 | if (
34 | (!once && data.toString().indexOf('Compiled successfully') >= 0) ||
35 | data.toString().indexOf('Theme generated successfully') >= 0
36 | ) {
37 | // eslint-disable-next-line
38 | once = true;
39 | console.log('Development server is started, ready to run tests.');
40 | const testCmd = spawn(
41 | /^win/.test(process.platform) ? 'npm.cmd' : 'npm',
42 | ['test', '--', '--maxWorkers=1', '--runInBand'],
43 | {
44 | stdio: 'inherit',
45 | },
46 | );
47 | testCmd.on('exit', code => {
48 | startServer.kill();
49 | process.exit(code);
50 | });
51 | }
52 | });
53 |
--------------------------------------------------------------------------------