Ver código fonte

first commit

张延森 5 anos atrás
commit
659fc3bc6c
100 arquivos alterados com 4401 adições e 0 exclusões
  1. 16
    0
      .editorconfig
  2. 3
    0
      .eslintignore
  3. 9
    0
      .eslintrc.js
  4. 40
    0
      .gitignore
  5. 19
    0
      .prettierignore
  6. 5
    0
      .prettierrc.js
  7. 5
    0
      .stylelintrc.js
  8. 57
    0
      README.md
  9. 180
    0
      config/config.js
  10. 16
    0
      config/defaultSettings.js
  11. 112
    0
      config/plugin.config.js
  12. 12
    0
      jest-puppeteer.config.js
  13. 7
    0
      jest.config.js
  14. 10
    0
      jsconfig.json
  15. 103
    0
      mock/notices.js
  16. 7
    0
      mock/route.js
  17. 148
    0
      mock/user.js
  18. 121
    0
      package.json
  19. BIN
      public/favicon.png
  20. BIN
      public/icons/icon-128x128.png
  21. BIN
      public/icons/icon-192x192.png
  22. BIN
      public/icons/icon-512x512.png
  23. BIN
      src/assets/login-bg.png
  24. 43
    0
      src/assets/logo.svg
  25. 10
    0
      src/components/Authorized/Authorized.jsx
  26. 25
    0
      src/components/Authorized/AuthorizedRoute.jsx
  27. 76
    0
      src/components/Authorized/CheckPermissions.jsx
  28. 78
    0
      src/components/Authorized/PromiseRender.jsx
  29. 70
    0
      src/components/Authorized/Secured.jsx
  30. 10
    0
      src/components/Authorized/index.jsx
  31. 30
    0
      src/components/Authorized/renderAuthorize.js
  32. 19
    0
      src/components/Cell/index.jsx
  33. 12
    0
      src/components/Cell/style.less
  34. 71
    0
      src/components/CopyBlock/index.jsx
  35. 29
    0
      src/components/CopyBlock/index.less
  36. 79
    0
      src/components/GlobalHeader/AvatarDropdown.jsx
  37. 174
    0
      src/components/GlobalHeader/NoticeIconView.jsx
  38. 66
    0
      src/components/GlobalHeader/RightContent.jsx
  39. 136
    0
      src/components/GlobalHeader/index.less
  40. 10
    0
      src/components/HeaderDropdown/index.jsx
  41. 16
    0
      src/components/HeaderDropdown/index.less
  42. 132
    0
      src/components/HeaderSearch/index.jsx
  43. 32
    0
      src/components/HeaderSearch/index.less
  44. 95
    0
      src/components/NoticeIcon/NoticeList.jsx
  45. 105
    0
      src/components/NoticeIcon/NoticeList.less
  46. 155
    0
      src/components/NoticeIcon/index.jsx
  47. 31
    0
      src/components/NoticeIcon/index.less
  48. 16
    0
      src/components/PageLoading/index.jsx
  49. 53
    0
      src/components/SelectLang/index.jsx
  50. 24
    0
      src/components/SelectLang/index.less
  51. 33
    0
      src/components/SettingDrawer/themeColorClient.js
  52. 1
    0
      src/e2e/__mocks__/antd-pro-merge-less.js
  53. 39
    0
      src/e2e/baseLayout.e2e.js
  54. 15
    0
      src/e2e/topMenu.e2e.js
  55. 101
    0
      src/global.jsx
  56. 61
    0
      src/global.less
  57. 129
    0
      src/layouts/BasicLayout.jsx
  58. 5
    0
      src/layouts/BlankLayout.jsx
  59. 43
    0
      src/layouts/SecurityLayout.jsx
  60. 43
    0
      src/layouts/UserLayout.jsx
  61. 72
    0
      src/layouts/UserLayout.less
  62. 21
    0
      src/locales/en-US.js
  63. 5
    0
      src/locales/en-US/component.js
  64. 17
    0
      src/locales/en-US/globalHeader.js
  65. 50
    0
      src/locales/en-US/menu.js
  66. 6
    0
      src/locales/en-US/pwa.js
  67. 31
    0
      src/locales/en-US/settingDrawer.js
  68. 60
    0
      src/locales/en-US/settings.js
  69. 19
    0
      src/locales/pt-BR.js
  70. 5
    0
      src/locales/pt-BR/component.js
  71. 18
    0
      src/locales/pt-BR/globalHeader.js
  72. 50
    0
      src/locales/pt-BR/menu.js
  73. 7
    0
      src/locales/pt-BR/pwa.js
  74. 32
    0
      src/locales/pt-BR/settingDrawer.js
  75. 60
    0
      src/locales/pt-BR/settings.js
  76. 21
    0
      src/locales/zh-CN.js
  77. 5
    0
      src/locales/zh-CN/component.js
  78. 17
    0
      src/locales/zh-CN/globalHeader.js
  79. 50
    0
      src/locales/zh-CN/menu.js
  80. 6
    0
      src/locales/zh-CN/pwa.js
  81. 31
    0
      src/locales/zh-CN/settingDrawer.js
  82. 55
    0
      src/locales/zh-CN/settings.js
  83. 19
    0
      src/locales/zh-TW.js
  84. 5
    0
      src/locales/zh-TW/component.js
  85. 17
    0
      src/locales/zh-TW/globalHeader.js
  86. 50
    0
      src/locales/zh-TW/menu.js
  87. 6
    0
      src/locales/zh-TW/pwa.js
  88. 31
    0
      src/locales/zh-TW/settingDrawer.js
  89. 55
    0
      src/locales/zh-TW/settings.js
  90. 22
    0
      src/manifest.json
  91. 114
    0
      src/models/global.js
  92. 69
    0
      src/models/login.js
  93. 82
    0
      src/models/setting.js
  94. 46
    0
      src/models/user.js
  95. 19
    0
      src/pages/404.jsx
  96. 50
    0
      src/pages/Authorized.jsx
  97. 61
    0
      src/pages/UserManage/index.jsx
  98. 12
    0
      src/pages/UserManage/style.less
  99. 168
    0
      src/pages/document.ejs
  100. 0
    0
      src/pages/user/login/components/Login/LoginContext.jsx

+ 16
- 0
.editorconfig Ver arquivo

@@ -0,0 +1,16 @@
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

+ 3
- 0
.eslintignore Ver arquivo

@@ -0,0 +1,3 @@
1
+/lambda/
2
+/scripts
3
+/config

+ 9
- 0
.eslintrc.js Ver arquivo

@@ -0,0 +1,9 @@
1
+const { strictEslint } = require('@umijs/fabric');
2
+
3
+module.exports = {
4
+  ...strictEslint,
5
+  globals: {
6
+    ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
7
+    page: true,
8
+  },
9
+};

+ 40
- 0
.gitignore Ver arquivo

@@ -0,0 +1,40 @@
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

+ 19
- 0
.prettierignore Ver arquivo

@@ -0,0 +1,19 @@
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

+ 5
- 0
.prettierrc.js Ver arquivo

@@ -0,0 +1,5 @@
1
+const fabric = require('@umijs/fabric');
2
+
3
+module.exports = {
4
+  ...fabric.prettier,
5
+};

+ 5
- 0
.stylelintrc.js Ver arquivo

@@ -0,0 +1,5 @@
1
+const fabric = require('@umijs/fabric');
2
+
3
+module.exports = {
4
+  ...fabric.stylelint,
5
+};

+ 57
- 0
README.md Ver arquivo

@@ -0,0 +1,57 @@
1
+# Ant Design Pro
2
+
3
+This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.
4
+
5
+## Environment Prepare
6
+
7
+Install `node_modules`:
8
+
9
+```bash
10
+npm install
11
+```
12
+
13
+or
14
+
15
+```bash
16
+yarn
17
+```
18
+
19
+## Provided Scripts
20
+
21
+Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test.
22
+
23
+Scripts provided in `package.json`. It's safe to modify or add additional script:
24
+
25
+### Start project
26
+
27
+```bash
28
+npm start
29
+```
30
+
31
+### Build project
32
+
33
+```bash
34
+npm run build
35
+```
36
+
37
+### Check code style
38
+
39
+```bash
40
+npm run lint
41
+```
42
+
43
+You can also use script to auto fix some lint error:
44
+
45
+```bash
46
+npm run lint:fix
47
+```
48
+
49
+### Test code
50
+
51
+```bash
52
+npm test
53
+```
54
+
55
+## More
56
+
57
+You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).

+ 180
- 0
config/config.js Ver arquivo

@@ -0,0 +1,180 @@
1
+import defaultSettings from './defaultSettings'; // https://umijs.org/config/
2
+
3
+import slash from 'slash2';
4
+import webpackPlugin from './plugin.config';
5
+const { pwa, primaryColor } = defaultSettings; // preview.pro.ant.design only do not use in your production ;
6
+// preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
7
+
8
+const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env;
9
+const isAntDesignProPreview = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site';
10
+const plugins = [
11
+  [
12
+    'umi-plugin-react',
13
+    {
14
+      antd: true,
15
+      dva: {
16
+        hmr: true,
17
+      },
18
+      locale: {
19
+        // default false
20
+        enable: true,
21
+        // default zh-CN
22
+        default: 'zh-CN',
23
+        // default true, when it is true, will use `navigator.language` overwrite default
24
+        baseNavigator: true,
25
+      },
26
+      // dynamicImport: {
27
+      //   loadingComponent: './components/PageLoading/index',
28
+      //   webpackChunkName: true,
29
+      //   level: 3,
30
+      // },
31
+      pwa: pwa
32
+        ? {
33
+            workboxPluginMode: 'InjectManifest',
34
+            workboxOptions: {
35
+              importWorkboxFrom: 'local',
36
+            },
37
+          }
38
+        : false, // default close dll, because issue https://github.com/ant-design/ant-design-pro/issues/4665
39
+      // dll features https://webpack.js.org/plugins/dll-plugin/
40
+      // dll: {
41
+      //   include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
42
+      //   exclude: ['@babel/runtime', 'netlify-lambda'],
43
+      // },
44
+    },
45
+  ],
46
+  [
47
+    'umi-plugin-pro-block',
48
+    {
49
+      moveMock: false,
50
+      moveService: false,
51
+      modifyRequest: true,
52
+      autoAddMenu: true,
53
+    },
54
+  ],
55
+]; // 针对 preview.pro.ant.design 的 GA 统计代码
56
+
57
+if (isAntDesignProPreview) {
58
+  plugins.push([
59
+    'umi-plugin-ga',
60
+    {
61
+      code: 'UA-72788897-6',
62
+    },
63
+  ]);
64
+  plugins.push([
65
+    'umi-plugin-pro',
66
+    {
67
+      serverUrl: 'https://ant-design-pro.netlify.com',
68
+    },
69
+  ]);
70
+}
71
+
72
+export default {
73
+  plugins,
74
+  block: {
75
+    // 国内用户可以使用码云
76
+    // defaultGitUrl: 'https://gitee.com/ant-design/pro-blocks',
77
+    defaultGitUrl: 'https://github.com/ant-design/pro-blocks',
78
+  },
79
+  hash: true,
80
+  targets: {
81
+    ie: 11,
82
+  },
83
+  devtool: isAntDesignProPreview ? 'source-map' : false,
84
+  // umi routes: https://umijs.org/zh/guide/router.html
85
+  routes: [
86
+    {
87
+      path: '/user',
88
+      component: '../layouts/UserLayout',
89
+      routes: [
90
+        {
91
+          name: 'login',
92
+          path: '/user/login',
93
+          component: './user/login',
94
+        },
95
+      ],
96
+    },
97
+    {
98
+      path: '/',
99
+      component: '../layouts/SecurityLayout',
100
+      routes: [
101
+        {
102
+          path: '/',
103
+          component: '../layouts/BasicLayout',
104
+          authority: ['admin', 'user'],
105
+          routes: [
106
+            {
107
+              path: '/',
108
+              redirect: '/org',
109
+            },
110
+            {
111
+              path: '/org',
112
+              name: '账户管理',
113
+              component: './UserManage',
114
+            },
115
+            {
116
+              component: './404',
117
+            },
118
+          ],
119
+        },
120
+        {
121
+          component: './404',
122
+        },
123
+      ],
124
+    },
125
+    {
126
+      component: './404',
127
+    },
128
+  ],
129
+  // Theme for antd: https://ant.design/docs/react/customize-theme-cn
130
+  theme: {
131
+    'primary-color': primaryColor,
132
+  },
133
+  define: {
134
+    ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION:
135
+      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 专用环境变量,请不要在你的项目中使用它。
136
+  },
137
+  ignoreMomentLocale: true,
138
+  lessLoaderOptions: {
139
+    javascriptEnabled: true,
140
+  },
141
+  disableRedirectHoist: true,
142
+  cssLoaderOptions: {
143
+    modules: true,
144
+    getLocalIdent: (context, _, localName) => {
145
+      if (
146
+        context.resourcePath.includes('node_modules') ||
147
+        context.resourcePath.includes('ant.design.pro.less') ||
148
+        context.resourcePath.includes('global.less')
149
+      ) {
150
+        return localName;
151
+      }
152
+
153
+      const match = context.resourcePath.match(/src(.*)/);
154
+
155
+      if (match && match[1]) {
156
+        const antdProPath = match[1].replace('.less', '');
157
+        const arr = slash(antdProPath)
158
+          .split('/')
159
+          .map(a => a.replace(/([A-Z])/g, '-$1'))
160
+          .map(a => a.toLowerCase());
161
+        return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
162
+      }
163
+
164
+      return localName;
165
+    },
166
+  },
167
+  manifest: {
168
+    basePath: '/',
169
+  },
170
+  chainWebpack: webpackPlugin,
171
+  /*
172
+  proxy: {
173
+    '/server/api/': {
174
+      target: 'https://preview.pro.ant.design/',
175
+      changeOrigin: true,
176
+      pathRewrite: { '^/server': '' },
177
+    },
178
+  },
179
+  */
180
+};

+ 16
- 0
config/defaultSettings.js Ver arquivo

@@ -0,0 +1,16 @@
1
+export default {
2
+  navTheme: 'light',
3
+  primaryColor: '#1890FF',
4
+  layout: 'topmenu',
5
+  contentWidth: 'Fluid',
6
+  fixedHeader: false,
7
+  autoHideHeader: false,
8
+  fixSiderbar: false,
9
+  colorWeak: false,
10
+  menu: {
11
+    locale: false,
12
+  },
13
+  title: '营销云服务平台',
14
+  pwa: false,
15
+  iconfontUrl: '',
16
+};

+ 112
- 0
config/plugin.config.js Ver arquivo

@@ -0,0 +1,112 @@
1
+// Change theme plugin
2
+// eslint-disable-next-line eslint-comments/abdeils - enable - pair;
3
+
4
+/* eslint-disable import/no-extraneous-dependencies */
5
+import ThemeColorReplacer from 'webpack-theme-color-replacer';
6
+import generate from '@ant-design/colors/lib/generate';
7
+import path from 'path';
8
+
9
+function getModulePackageName(module) {
10
+  if (!module.context) return null;
11
+  const nodeModulesPath = path.join(__dirname, '../node_modules/');
12
+
13
+  if (module.context.substring(0, nodeModulesPath.length) !== nodeModulesPath) {
14
+    return null;
15
+  }
16
+
17
+  const moduleRelativePath = module.context.substring(nodeModulesPath.length);
18
+  const [moduleDirName] = moduleRelativePath.split(path.sep);
19
+  let packageName = moduleDirName; // handle tree shaking
20
+
21
+  if (packageName && packageName.match('^_')) {
22
+    // eslint-disable-next-line prefer-destructuring
23
+    packageName = packageName.match(/^_(@?[^@]+)/)[1];
24
+  }
25
+
26
+  return packageName;
27
+}
28
+
29
+export default config => {
30
+  // preview.pro.ant.design only do not use in your production;
31
+  if (
32
+    process.env.ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ||
33
+    process.env.NODE_ENV !== 'production'
34
+  ) {
35
+    config.plugin('webpack-theme-color-replacer').use(ThemeColorReplacer, [
36
+      {
37
+        fileName: 'css/theme-colors-[contenthash:8].css',
38
+        matchColors: getAntdSerials('#1890ff'),
39
+
40
+        // 主色系列
41
+        // 改变样式选择器,解决样式覆盖问题
42
+        changeSelector(selector) {
43
+          switch (selector) {
44
+            case '.ant-calendar-today .ant-calendar-date':
45
+              return ':not(.ant-calendar-selected-date)' + selector;
46
+
47
+            case '.ant-btn:focus,.ant-btn:hover':
48
+              return '.ant-btn:focus:not(.ant-btn-primary),.ant-btn:hover:not(.ant-btn-primary)';
49
+
50
+            case '.ant-btn.active,.ant-btn:active':
51
+              return '.ant-btn.active:not(.ant-btn-primary),.ant-btn:active:not(.ant-btn-primary)';
52
+
53
+            default:
54
+              return selector;
55
+          }
56
+        }, // isJsUgly: true,
57
+      },
58
+    ]);
59
+  } // optimize chunks
60
+
61
+  config.optimization // share the same chunks across different modules
62
+    .runtimeChunk(false)
63
+    .splitChunks({
64
+      chunks: 'async',
65
+      name: 'vendors',
66
+      maxInitialRequests: Infinity,
67
+      minSize: 0,
68
+      cacheGroups: {
69
+        vendors: {
70
+          test: module => {
71
+            const packageName = getModulePackageName(module) || '';
72
+
73
+            if (packageName) {
74
+              return [
75
+                'bizcharts',
76
+                'gg-editor',
77
+                'g6',
78
+                '@antv',
79
+                'gg-editor-core',
80
+                'bizcharts-plugin-slider',
81
+              ].includes(packageName);
82
+            }
83
+
84
+            return false;
85
+          },
86
+
87
+          name(module) {
88
+            const packageName = getModulePackageName(module);
89
+
90
+            if (packageName) {
91
+              if (['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0) {
92
+                return 'viz'; // visualization package
93
+              }
94
+            }
95
+
96
+            return 'misc';
97
+          },
98
+        },
99
+      },
100
+    });
101
+};
102
+
103
+const getAntdSerials = color => {
104
+  const lightNum = 9;
105
+  const devide10 = 10; // 淡化(即less的tint)
106
+
107
+  const lightens = new Array(lightNum).fill(undefined).map((_, i) => {
108
+    return ThemeColorReplacer.varyColor.lighten(color, i / devide10);
109
+  });
110
+  const colorPalettes = generate(color);
111
+  return lightens.concat(colorPalettes);
112
+};

+ 12
- 0
jest-puppeteer.config.js Ver arquivo

@@ -0,0 +1,12 @@
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
+};

+ 7
- 0
jest.config.js Ver arquivo

@@ -0,0 +1,7 @@
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
+  },
7
+};

+ 10
- 0
jsconfig.json Ver arquivo

@@ -0,0 +1,10 @@
1
+{
2
+  "compilerOptions": {
3
+    "emitDecoratorMetadata": true,
4
+    "experimentalDecorators": true,
5
+    "baseUrl": ".",
6
+    "paths": {
7
+      "@/*": ["./src/*"]
8
+    }
9
+  }
10
+}

+ 103
- 0
mock/notices.js Ver arquivo

@@ -0,0 +1,103 @@
1
+const getNotices = (req, res) => {
2
+  res.json([
3
+    {
4
+      id: '000000001',
5
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
6
+      title: '你收到了 14 份新周报',
7
+      datetime: '2017-08-09',
8
+      type: 'notification',
9
+    },
10
+    {
11
+      id: '000000002',
12
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
13
+      title: '你推荐的 曲妮妮 已通过第三轮面试',
14
+      datetime: '2017-08-08',
15
+      type: 'notification',
16
+    },
17
+    {
18
+      id: '000000003',
19
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
20
+      title: '这种模板可以区分多种通知类型',
21
+      datetime: '2017-08-07',
22
+      read: true,
23
+      type: 'notification',
24
+    },
25
+    {
26
+      id: '000000004',
27
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
28
+      title: '左侧图标用于区分不同的类型',
29
+      datetime: '2017-08-07',
30
+      type: 'notification',
31
+    },
32
+    {
33
+      id: '000000005',
34
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
35
+      title: '内容不要超过两行字,超出时自动截断',
36
+      datetime: '2017-08-07',
37
+      type: 'notification',
38
+    },
39
+    {
40
+      id: '000000006',
41
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
42
+      title: '曲丽丽 评论了你',
43
+      description: '描述信息描述信息描述信息',
44
+      datetime: '2017-08-07',
45
+      type: 'message',
46
+      clickClose: true,
47
+    },
48
+    {
49
+      id: '000000007',
50
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
51
+      title: '朱偏右 回复了你',
52
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
53
+      datetime: '2017-08-07',
54
+      type: 'message',
55
+      clickClose: true,
56
+    },
57
+    {
58
+      id: '000000008',
59
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
60
+      title: '标题',
61
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
62
+      datetime: '2017-08-07',
63
+      type: 'message',
64
+      clickClose: true,
65
+    },
66
+    {
67
+      id: '000000009',
68
+      title: '任务名称',
69
+      description: '任务需要在 2017-01-12 20:00 前启动',
70
+      extra: '未开始',
71
+      status: 'todo',
72
+      type: 'event',
73
+    },
74
+    {
75
+      id: '000000010',
76
+      title: '第三方紧急代码变更',
77
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
78
+      extra: '马上到期',
79
+      status: 'urgent',
80
+      type: 'event',
81
+    },
82
+    {
83
+      id: '000000011',
84
+      title: '信息安全考试',
85
+      description: '指派竹尔于 2017-01-09 前完成更新并发布',
86
+      extra: '已耗时 8 天',
87
+      status: 'doing',
88
+      type: 'event',
89
+    },
90
+    {
91
+      id: '000000012',
92
+      title: 'ABCD 版本发布',
93
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
94
+      extra: '进行中',
95
+      status: 'processing',
96
+      type: 'event',
97
+    },
98
+  ]);
99
+};
100
+
101
+export default {
102
+  'GET /api/notices': getNotices,
103
+};

+ 7
- 0
mock/route.js Ver arquivo

@@ -0,0 +1,7 @@
1
+export default {
2
+  '/api/auth_routes': {
3
+    '/form/advanced-form': {
4
+      authority: ['admin', 'user'],
5
+    },
6
+  },
7
+};

+ 148
- 0
mock/user.js Ver arquivo

@@ -0,0 +1,148 @@
1
+function getFakeCaptcha(req, res) {
2
+  return res.json('captcha-xxx');
3
+} // 代码中会兼容本地 service mock 以及部署站点的静态数据
4
+
5
+export default {
6
+  // 支持值为 Object 和 Array
7
+  'GET /api/currentUser': {
8
+    name: 'Serati Ma',
9
+    avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
10
+    userid: '00000001',
11
+    email: 'antdesign@alipay.com',
12
+    signature: '海纳百川,有容乃大',
13
+    title: '交互专家',
14
+    group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
15
+    tags: [
16
+      {
17
+        key: '0',
18
+        label: '很有想法的',
19
+      },
20
+      {
21
+        key: '1',
22
+        label: '专注设计',
23
+      },
24
+      {
25
+        key: '2',
26
+        label: '辣~',
27
+      },
28
+      {
29
+        key: '3',
30
+        label: '大长腿',
31
+      },
32
+      {
33
+        key: '4',
34
+        label: '川妹子',
35
+      },
36
+      {
37
+        key: '5',
38
+        label: '海纳百川',
39
+      },
40
+    ],
41
+    notifyCount: 12,
42
+    unreadCount: 11,
43
+    country: 'China',
44
+    geographic: {
45
+      province: {
46
+        label: '浙江省',
47
+        key: '330000',
48
+      },
49
+      city: {
50
+        label: '杭州市',
51
+        key: '330100',
52
+      },
53
+    },
54
+    address: '西湖区工专路 77 号',
55
+    phone: '0752-268888888',
56
+  },
57
+  // GET POST 可省略
58
+  'GET /api/users': [
59
+    {
60
+      key: '1',
61
+      name: 'John Brown',
62
+      age: 32,
63
+      address: 'New York No. 1 Lake Park',
64
+    },
65
+    {
66
+      key: '2',
67
+      name: 'Jim Green',
68
+      age: 42,
69
+      address: 'London No. 1 Lake Park',
70
+    },
71
+    {
72
+      key: '3',
73
+      name: 'Joe Black',
74
+      age: 32,
75
+      address: 'Sidney No. 1 Lake Park',
76
+    },
77
+  ],
78
+  'POST /api/login/account': (req, res) => {
79
+    const { password, userName, type } = req.body;
80
+
81
+    if (password === 'ant.design' && userName === 'admin') {
82
+      res.send({
83
+        status: 'ok',
84
+        type,
85
+        currentAuthority: 'admin',
86
+      });
87
+      return;
88
+    }
89
+
90
+    if (password === 'ant.design' && userName === 'user') {
91
+      res.send({
92
+        status: 'ok',
93
+        type,
94
+        currentAuthority: 'user',
95
+      });
96
+      return;
97
+    }
98
+
99
+    res.send({
100
+      status: 'error',
101
+      type,
102
+      currentAuthority: 'guest',
103
+    });
104
+  },
105
+  'POST /api/register': (req, res) => {
106
+    res.send({
107
+      status: 'ok',
108
+      currentAuthority: 'user',
109
+    });
110
+  },
111
+  'GET /api/500': (req, res) => {
112
+    res.status(500).send({
113
+      timestamp: 1513932555104,
114
+      status: 500,
115
+      error: 'error',
116
+      message: 'error',
117
+      path: '/base/category/list',
118
+    });
119
+  },
120
+  'GET /api/404': (req, res) => {
121
+    res.status(404).send({
122
+      timestamp: 1513932643431,
123
+      status: 404,
124
+      error: 'Not Found',
125
+      message: 'No message available',
126
+      path: '/base/category/list/2121212',
127
+    });
128
+  },
129
+  'GET /api/403': (req, res) => {
130
+    res.status(403).send({
131
+      timestamp: 1513932555104,
132
+      status: 403,
133
+      error: 'Unauthorized',
134
+      message: 'Unauthorized',
135
+      path: '/base/category/list',
136
+    });
137
+  },
138
+  'GET /api/401': (req, res) => {
139
+    res.status(401).send({
140
+      timestamp: 1513932555104,
141
+      status: 401,
142
+      error: 'Unauthorized',
143
+      message: 'Unauthorized',
144
+      path: '/base/category/list',
145
+    });
146
+  },
147
+  'GET  /api/login/captcha': getFakeCaptcha,
148
+};

+ 121
- 0
package.json Ver arquivo

@@ -0,0 +1,121 @@
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": "cross-env ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION=site npm run site && npm run gh-pages",
10
+    "fetch:blocks": "pro fetch-blocks",
11
+    "format-imports": "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
+    "test": "umi test",
25
+    "test:all": "node ./tests/run-tests.js",
26
+    "test:component": "umi test ./src/components",
27
+    "ui": "umi ui"
28
+  },
29
+  "husky": {
30
+    "hooks": {
31
+      "pre-commit": "npm run lint-staged"
32
+    }
33
+  },
34
+  "lint-staged": {
35
+    "**/*.less": "stylelint --syntax less",
36
+    "**/*.{js,jsx,tsx,ts,less,md,json}": [
37
+      "prettier --write",
38
+      "git add"
39
+    ],
40
+    "**/*.{js,jsx}": "npm run lint-staged:js",
41
+    "**/*.{js,ts,tsx}": "npm run lint-staged:js"
42
+  },
43
+  "browserslist": [
44
+    "> 1%",
45
+    "last 2 versions",
46
+    "not ie <= 10"
47
+  ],
48
+  "dependencies": {
49
+    "@ant-design/pro-layout": "^4.5.9",
50
+    "@antv/data-set": "^0.10.2",
51
+    "antd": "^3.20.0",
52
+    "classnames": "^2.2.6",
53
+    "dva": "^2.4.1",
54
+    "lodash": "^4.17.11",
55
+    "moment": "^2.24.0",
56
+    "omit.js": "^1.0.2",
57
+    "path-to-regexp": "^3.0.0",
58
+    "qs": "^6.7.0",
59
+    "react": "^16.8.6",
60
+    "react-copy-to-clipboard": "^5.0.1",
61
+    "react-document-title": "^2.0.3",
62
+    "react-dom": "^16.8.6",
63
+    "redux": "^4.0.1",
64
+    "umi": "^2.8.7",
65
+    "umi-plugin-pro-block": "^1.3.2",
66
+    "umi-plugin-react": "^1.9.5",
67
+    "umi-request": "^1.0.8"
68
+  },
69
+  "devDependencies": {
70
+    "@ant-design/colors": "^3.1.0",
71
+    "@ant-design/pro-cli": "^1.0.0",
72
+    "@types/classnames": "^2.2.7",
73
+    "@types/express": "^4.17.0",
74
+    "@types/history": "^4.7.2",
75
+    "@types/jest": "^24.0.13",
76
+    "@types/lodash": "^4.14.133",
77
+    "@types/qs": "^6.5.3",
78
+    "@types/react": "^16.8.19",
79
+    "@types/react-document-title": "^2.0.3",
80
+    "@types/react-dom": "^16.8.4",
81
+    "@umijs/fabric": "^1.1.0",
82
+    "chalk": "^2.4.2",
83
+    "check-prettier": "^1.0.3",
84
+    "cross-env": "^5.2.0",
85
+    "cross-port-killer": "^1.1.1",
86
+    "enzyme": "^3.9.0",
87
+    "eslint": "^5.16.0",
88
+    "express": "^4.17.1",
89
+    "gh-pages": "^2.0.1",
90
+    "husky": "^3.0.0",
91
+    "import-sort-cli": "^6.0.0",
92
+    "import-sort-parser-babylon": "^6.0.0",
93
+    "import-sort-parser-typescript": "^6.0.0",
94
+    "import-sort-style-module": "^6.0.0",
95
+    "jest-puppeteer": "^4.2.0",
96
+    "lint-staged": "^9.0.0",
97
+    "mockjs": "^1.0.1-beta3",
98
+    "node-fetch": "^2.6.0",
99
+    "prettier": "^1.17.1",
100
+    "pro-download": "1.0.1",
101
+    "slash2": "^2.0.0",
102
+    "stylelint": "^10.1.0",
103
+    "umi-plugin-ga": "^1.1.3",
104
+    "umi-plugin-pro": "^1.0.2",
105
+    "umi-types": "^0.3.8",
106
+    "webpack-theme-color-replacer": "^1.2.15"
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
+}

BIN
public/favicon.png Ver arquivo


BIN
public/icons/icon-128x128.png Ver arquivo


BIN
public/icons/icon-192x192.png Ver arquivo


BIN
public/icons/icon-512x512.png Ver arquivo


BIN
src/assets/login-bg.png Ver arquivo


+ 43
- 0
src/assets/logo.svg Ver arquivo

@@ -0,0 +1,43 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+    <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
4
+    <title>Group 28 Copy 5</title>
5
+    <desc>Created with Sketch.</desc>
6
+    <defs>
7
+        <linearGradient x1="62.1023273%" y1="0%" x2="108.19718%" y2="37.8635764%" id="linearGradient-1">
8
+            <stop stop-color="#4285EB" offset="0%"></stop>
9
+            <stop stop-color="#2EC7FF" offset="100%"></stop>
10
+        </linearGradient>
11
+        <linearGradient x1="69.644116%" y1="0%" x2="54.0428975%" y2="108.456714%" id="linearGradient-2">
12
+            <stop stop-color="#29CDFF" offset="0%"></stop>
13
+            <stop stop-color="#148EFF" offset="37.8600687%"></stop>
14
+            <stop stop-color="#0A60FF" offset="100%"></stop>
15
+        </linearGradient>
16
+        <linearGradient x1="69.6908165%" y1="-12.9743587%" x2="16.7228981%" y2="117.391248%" id="linearGradient-3">
17
+            <stop stop-color="#FA816E" offset="0%"></stop>
18
+            <stop stop-color="#F74A5C" offset="41.472606%"></stop>
19
+            <stop stop-color="#F51D2C" offset="100%"></stop>
20
+        </linearGradient>
21
+        <linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-4">
22
+            <stop stop-color="#FA8E7D" offset="0%"></stop>
23
+            <stop stop-color="#F74A5C" offset="51.2635191%"></stop>
24
+            <stop stop-color="#F51D2C" offset="100%"></stop>
25
+        </linearGradient>
26
+    </defs>
27
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
28
+        <g id="logo" transform="translate(-20.000000, -20.000000)">
29
+            <g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)">
30
+                <g id="Group-27-Copy-3">
31
+                    <g id="Group-25" fill-rule="nonzero">
32
+                        <g id="2">
33
+                            <path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-1)"></path>
34
+                            <path d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z" id="Shape" fill="url(#linearGradient-2)"></path>
35
+                        </g>
36
+                        <path d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z" id="Shape" fill="url(#linearGradient-3)"></path>
37
+                    </g>
38
+                    <ellipse id="Combined-Shape" fill="url(#linearGradient-4)" cx="100.519339" cy="100.436681" rx="23.6001926" ry="23.580786"></ellipse>
39
+                </g>
40
+            </g>
41
+        </g>
42
+    </g>
43
+</svg>

+ 10
- 0
src/components/Authorized/Authorized.jsx Ver arquivo

@@ -0,0 +1,10 @@
1
+import React from 'react';
2
+import check from './CheckPermissions';
3
+
4
+const Authorized = ({ children, authority, noMatch = null }) => {
5
+  const childrenRender = typeof children === 'undefined' ? null : children;
6
+  const dom = check(authority, childrenRender, noMatch);
7
+  return <>{dom}</>;
8
+};
9
+
10
+export default Authorized;

+ 25
- 0
src/components/Authorized/AuthorizedRoute.jsx Ver arquivo

@@ -0,0 +1,25 @@
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
+  <Authorized
7
+    authority={authority}
8
+    noMatch={
9
+      <Route
10
+        {...rest}
11
+        render={() => (
12
+          <Redirect
13
+            to={{
14
+              pathname: redirectPath,
15
+            }}
16
+          />
17
+        )}
18
+      />
19
+    }
20
+  >
21
+    <Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
22
+  </Authorized>
23
+);
24
+
25
+export default AuthorizedRoute;

+ 76
- 0
src/components/Authorized/CheckPermissions.jsx Ver arquivo

@@ -0,0 +1,76 @@
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 <PromiseRender ok={target} error={Exception} promise={authority} />;
47
+  } // Function 处理
48
+
49
+  if (typeof authority === 'function') {
50
+    try {
51
+      const bool = authority(currentAuthority); // 函数执行后返回值是 Promise
52
+
53
+      if (bool instanceof Promise) {
54
+        return <PromiseRender ok={target} error={Exception} promise={bool} />;
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;

+ 78
- 0
src/components/Authorized/PromiseRender.jsx Ver arquivo

@@ -0,0 +1,78 @@
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 => <Target {...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
+      <Component {...rest} />
64
+    ) : (
65
+      <div
66
+        style={{
67
+          width: '100%',
68
+          height: '100%',
69
+          margin: 'auto',
70
+          paddingTop: 50,
71
+          textAlign: 'center',
72
+        }}
73
+      >
74
+        <Spin size="large" />
75
+      </div>
76
+    );
77
+  }
78
+}

+ 70
- 0
src/components/Authorized/Secured.jsx Ver arquivo

@@ -0,0 +1,70 @@
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 => <Target {...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;

+ 10
- 0
src/components/Authorized/index.jsx Ver arquivo

@@ -0,0 +1,10 @@
1
+import Authorized from './Authorized';
2
+import AuthorizedRoute from './AuthorizedRoute';
3
+import Secured from './Secured';
4
+import check from './CheckPermissions';
5
+import renderAuthorize from './renderAuthorize';
6
+Authorized.Secured = Secured;
7
+Authorized.AuthorizedRoute = AuthorizedRoute;
8
+Authorized.check = check;
9
+const RenderAuthorize = renderAuthorize(Authorized);
10
+export default RenderAuthorize;

+ 30
- 0
src/components/Authorized/renderAuthorize.js Ver arquivo

@@ -0,0 +1,30 @@
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);

+ 19
- 0
src/components/Cell/index.jsx Ver arquivo

@@ -0,0 +1,19 @@
1
+import React from 'react';
2
+import Style from './style.less';
3
+
4
+export default (props) => {
5
+  const { children, action, ...otherProps } = props || {};
6
+
7
+  return (
8
+    <div className={Style['cell-wrapper']} {...otherProps}>
9
+      <div className={Style['cell-body']}>
10
+        {children}
11
+      </div>
12
+      { action && (
13
+        <div className={Style['cell-action']}>
14
+          {action}
15
+        </div>)
16
+      }
17
+    </div>
18
+  )
19
+}

+ 12
- 0
src/components/Cell/style.less Ver arquivo

@@ -0,0 +1,12 @@
1
+.cell-wrapper {
2
+  display: flex;
3
+  align-items: center;
4
+
5
+  .cell-body {
6
+    flex: auto;
7
+  }
8
+
9
+  .cell-action {
10
+    flex: none;
11
+  }
12
+}

+ 71
- 0
src/components/CopyBlock/index.jsx Ver arquivo

@@ -0,0 +1,71 @@
1
+import { Icon, Popover, Typography } from 'antd';
2
+import React, { useRef } from 'react';
3
+import { FormattedMessage } from 'umi-plugin-react/locale';
4
+import { connect } from 'dva';
5
+import { isAntDesignPro } from '@/utils/utils';
6
+import styles from './index.less';
7
+
8
+const firstUpperCase = pathString =>
9
+  pathString
10
+    .replace('.', '')
11
+    .split(/\/|-/)
12
+    .map(s => s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()))
13
+    .filter(s => !!s)
14
+    .join(''); // when  click block copy, send block url to  ga
15
+
16
+const onBlockCopy = label => {
17
+  if (!isAntDesignPro()) {
18
+    return;
19
+  }
20
+
21
+  const ga = window && window.ga;
22
+
23
+  if (ga) {
24
+    ga('send', 'event', {
25
+      eventCategory: 'block',
26
+      eventAction: 'copy',
27
+      eventLabel: label,
28
+    });
29
+  }
30
+};
31
+
32
+const BlockCodeView = ({ url }) => {
33
+  const blockUrl = `npx umi block add ${firstUpperCase(url)} --path=${url}`;
34
+  return (
35
+    <div className={styles['copy-block-view']}>
36
+      <Typography.Paragraph
37
+        copyable={{
38
+          text: blockUrl,
39
+          onCopy: () => onBlockCopy(url),
40
+        }}
41
+        style={{
42
+          display: 'flex',
43
+        }}
44
+      >
45
+        <pre>
46
+          <code className={styles['copy-block-code']}>{blockUrl}</code>
47
+        </pre>
48
+      </Typography.Paragraph>
49
+    </div>
50
+  );
51
+};
52
+
53
+export default connect(({ routing }) => ({
54
+  location: routing.location,
55
+}))(({ location }) => {
56
+  const url = location.pathname;
57
+  const divDom = useRef(null);
58
+  return (
59
+    <Popover
60
+      title={<FormattedMessage id="app.preview.down.block" defaultMessage="下载此页面到本地项目" />}
61
+      placement="topLeft"
62
+      content={<BlockCodeView url={url} />}
63
+      trigger="click"
64
+      getPopupContainer={dom => (divDom.current ? divDom.current : dom)}
65
+    >
66
+      <div className={styles['copy-block']} ref={divDom}>
67
+        <Icon type="download" />
68
+      </div>
69
+    </Popover>
70
+  );
71
+});

+ 29
- 0
src/components/CopyBlock/index.less Ver arquivo

@@ -0,0 +1,29 @@
1
+.copy-block {
2
+  position: fixed;
3
+  right: 80px;
4
+  bottom: 40px;
5
+  z-index: 99;
6
+  display: flex;
7
+  flex-direction: column;
8
+  align-items: center;
9
+  justify-content: center;
10
+  width: 40px;
11
+  height: 40px;
12
+  font-size: 20px;
13
+  background: #fff;
14
+  border-radius: 40px;
15
+  box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14),
16
+    0 1px 10px 0 rgba(0, 0, 0, 0.12);
17
+  cursor: pointer;
18
+}
19
+
20
+.copy-block-view {
21
+  position: relative;
22
+  .copy-block-code {
23
+    display: inline-block;
24
+    margin: 0 0.2em;
25
+    padding: 0.2em 0.4em 0.1em;
26
+    font-size: 85%;
27
+    border-radius: 3px;
28
+  }
29
+}

+ 79
- 0
src/components/GlobalHeader/AvatarDropdown.jsx Ver arquivo

@@ -0,0 +1,79 @@
1
+import { Avatar, Icon, Menu, Spin } from 'antd';
2
+import { FormattedMessage } from 'umi-plugin-react/locale';
3
+import React from 'react';
4
+import { connect } from 'dva';
5
+import router from 'umi/router';
6
+import HeaderDropdown from '../HeaderDropdown';
7
+import styles from './index.less';
8
+
9
+class AvatarDropdown extends React.Component {
10
+  onMenuClick = event => {
11
+    const { key } = event;
12
+
13
+    if (key === 'logout') {
14
+      const { dispatch } = this.props;
15
+
16
+      if (dispatch) {
17
+        dispatch({
18
+          type: 'login/logout',
19
+        });
20
+      }
21
+
22
+      return;
23
+    }
24
+
25
+    router.push(`/account/${key}`);
26
+  };
27
+
28
+  render() {
29
+    const {
30
+      currentUser = {
31
+        avatar: '',
32
+        name: '',
33
+      },
34
+      menu,
35
+    } = this.props;
36
+    const menuHeaderDropdown = (
37
+      <Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
38
+        {menu && (
39
+          <Menu.Item key="center">
40
+            <Icon type="user" />
41
+            <FormattedMessage id="menu.account.center" defaultMessage="account center" />
42
+          </Menu.Item>
43
+        )}
44
+        {menu && (
45
+          <Menu.Item key="settings">
46
+            <Icon type="setting" />
47
+            <FormattedMessage id="menu.account.settings" defaultMessage="account settings" />
48
+          </Menu.Item>
49
+        )}
50
+        {menu && <Menu.Divider />}
51
+
52
+        <Menu.Item key="logout">
53
+          <Icon type="logout" />
54
+          <FormattedMessage id="menu.account.logout" defaultMessage="logout" />
55
+        </Menu.Item>
56
+      </Menu>
57
+    );
58
+    return currentUser && currentUser.name ? (
59
+      <HeaderDropdown overlay={menuHeaderDropdown}>
60
+        <span className={`${styles.action} ${styles.account}`}>
61
+          <Avatar size="small" className={styles.avatar} src={currentUser.avatar} alt="avatar" />
62
+          <span className={styles.name}>{currentUser.name}</span>
63
+        </span>
64
+      </HeaderDropdown>
65
+    ) : (
66
+      <Spin
67
+        size="small"
68
+        style={{
69
+          marginLeft: 8,
70
+          marginRight: 8,
71
+        }}
72
+      />
73
+    );
74
+  }
75
+}
76
+
77
+export default connect(({ user }) => ({
78
+  currentUser: user.currentUser,
79
+}))(AvatarDropdown);

+ 174
- 0
src/components/GlobalHeader/NoticeIconView.jsx Ver arquivo

@@ -0,0 +1,174 @@
1
+import React, { Component } from 'react';
2
+import { Tag, message } from 'antd';
3
+import { connect } from 'dva';
4
+import { formatMessage } from 'umi-plugin-react/locale';
5
+import groupBy from 'lodash/groupBy';
6
+import moment from 'moment';
7
+import NoticeIcon from '../NoticeIcon';
8
+import styles from './index.less';
9
+
10
+class GlobalHeaderRight extends Component {
11
+  componentDidMount() {
12
+    const { dispatch } = this.props;
13
+
14
+    if (dispatch) {
15
+      dispatch({
16
+        type: 'global/fetchNotices',
17
+      });
18
+    }
19
+  }
20
+
21
+  changeReadState = clickedItem => {
22
+    const { id } = clickedItem;
23
+    const { dispatch } = this.props;
24
+
25
+    if (dispatch) {
26
+      dispatch({
27
+        type: 'global/changeNoticeReadState',
28
+        payload: id,
29
+      });
30
+    }
31
+  };
32
+  handleNoticeClear = (title, key) => {
33
+    const { dispatch } = this.props;
34
+    message.success(
35
+      `${formatMessage({
36
+        id: 'component.noticeIcon.cleared',
37
+      })} ${title}`,
38
+    );
39
+
40
+    if (dispatch) {
41
+      dispatch({
42
+        type: 'global/clearNotices',
43
+        payload: key,
44
+      });
45
+    }
46
+  };
47
+  getNoticeData = () => {
48
+    const { notices = [] } = this.props;
49
+
50
+    if (notices.length === 0) {
51
+      return {};
52
+    }
53
+
54
+    const newNotices = notices.map(notice => {
55
+      const newNotice = { ...notice };
56
+
57
+      if (newNotice.datetime) {
58
+        newNotice.datetime = moment(notice.datetime).fromNow();
59
+      }
60
+
61
+      if (newNotice.id) {
62
+        newNotice.key = newNotice.id;
63
+      }
64
+
65
+      if (newNotice.extra && newNotice.status) {
66
+        const color = {
67
+          todo: '',
68
+          processing: 'blue',
69
+          urgent: 'red',
70
+          doing: 'gold',
71
+        }[newNotice.status];
72
+        newNotice.extra = (
73
+          <Tag
74
+            color={color}
75
+            style={{
76
+              marginRight: 0,
77
+            }}
78
+          >
79
+            {newNotice.extra}
80
+          </Tag>
81
+        );
82
+      }
83
+
84
+      return newNotice;
85
+    });
86
+    return groupBy(newNotices, 'type');
87
+  };
88
+  getUnreadData = noticeData => {
89
+    const unreadMsg = {};
90
+    Object.keys(noticeData).forEach(key => {
91
+      const value = noticeData[key];
92
+
93
+      if (!unreadMsg[key]) {
94
+        unreadMsg[key] = 0;
95
+      }
96
+
97
+      if (Array.isArray(value)) {
98
+        unreadMsg[key] = value.filter(item => !item.read).length;
99
+      }
100
+    });
101
+    return unreadMsg;
102
+  };
103
+
104
+  render() {
105
+    const { currentUser, fetchingNotices, onNoticeVisibleChange } = this.props;
106
+    const noticeData = this.getNoticeData();
107
+    const unreadMsg = this.getUnreadData(noticeData);
108
+    return (
109
+      <NoticeIcon
110
+        className={styles.action}
111
+        count={currentUser && currentUser.unreadCount}
112
+        onItemClick={item => {
113
+          this.changeReadState(item);
114
+        }}
115
+        loading={fetchingNotices}
116
+        clearText={formatMessage({
117
+          id: 'component.noticeIcon.clear',
118
+        })}
119
+        viewMoreText={formatMessage({
120
+          id: 'component.noticeIcon.view-more',
121
+        })}
122
+        onClear={this.handleNoticeClear}
123
+        onPopupVisibleChange={onNoticeVisibleChange}
124
+        onViewMore={() => message.info('Click on view more')}
125
+        clearClose
126
+      >
127
+        <NoticeIcon.Tab
128
+          tabKey="notification"
129
+          count={unreadMsg.notification}
130
+          list={noticeData.notification}
131
+          title={formatMessage({
132
+            id: 'component.globalHeader.notification',
133
+          })}
134
+          emptyText={formatMessage({
135
+            id: 'component.globalHeader.notification.empty',
136
+          })}
137
+          showViewMore
138
+        />
139
+        <NoticeIcon.Tab
140
+          tabKey="message"
141
+          count={unreadMsg.message}
142
+          list={noticeData.message}
143
+          title={formatMessage({
144
+            id: 'component.globalHeader.message',
145
+          })}
146
+          emptyText={formatMessage({
147
+            id: 'component.globalHeader.message.empty',
148
+          })}
149
+          showViewMore
150
+        />
151
+        <NoticeIcon.Tab
152
+          tabKey="event"
153
+          title={formatMessage({
154
+            id: 'component.globalHeader.event',
155
+          })}
156
+          emptyText={formatMessage({
157
+            id: 'component.globalHeader.event.empty',
158
+          })}
159
+          count={unreadMsg.event}
160
+          list={noticeData.event}
161
+          showViewMore
162
+        />
163
+      </NoticeIcon>
164
+    );
165
+  }
166
+}
167
+
168
+export default connect(({ user, global, loading }) => ({
169
+  currentUser: user.currentUser,
170
+  collapsed: global.collapsed,
171
+  fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
172
+  fetchingNotices: loading.effects['global/fetchNotices'],
173
+  notices: global.notices,
174
+}))(GlobalHeaderRight);

+ 66
- 0
src/components/GlobalHeader/RightContent.jsx Ver arquivo

@@ -0,0 +1,66 @@
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
+    <div className={className}>
20
+      {/* <HeaderSearch
21
+        className={`${styles.action} ${styles.search}`}
22
+        placeholder={formatMessage({
23
+          id: 'component.globalHeader.search',
24
+        })}
25
+        dataSource={[
26
+          formatMessage({
27
+            id: 'component.globalHeader.search.example1',
28
+          }),
29
+          formatMessage({
30
+            id: 'component.globalHeader.search.example2',
31
+          }),
32
+          formatMessage({
33
+            id: 'component.globalHeader.search.example3',
34
+          }),
35
+        ]}
36
+        onSearch={value => {
37
+          console.log('input', value);
38
+        }}
39
+        onPressEnter={value => {
40
+          console.log('enter', value);
41
+        }}
42
+      />
43
+      <Tooltip
44
+        title={formatMessage({
45
+          id: 'component.globalHeader.help',
46
+        })}
47
+      >
48
+        <a
49
+          target="_blank"
50
+          href="https://pro.ant.design/docs/getting-started"
51
+          rel="noopener noreferrer"
52
+          className={styles.action}
53
+        >
54
+          <Icon type="question-circle-o" />
55
+        </a>
56
+      </Tooltip> */}
57
+      <Avatar />
58
+      {/* <SelectLang className={styles.action} /> */}
59
+    </div>
60
+  );
61
+};
62
+
63
+export default connect(({ settings }) => ({
64
+  theme: settings.navTheme,
65
+  layout: settings.layout,
66
+}))(GlobalHeaderRight);

+ 136
- 0
src/components/GlobalHeader/index.less Ver arquivo

@@ -0,0 +1,136 @@
1
+@import '~antd/es/style/themes/default.less';
2
+
3
+@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
4
+
5
+.logo {
6
+  display: inline-block;
7
+  height: @layout-header-height;
8
+  padding: 0 0 0 24px;
9
+  font-size: 20px;
10
+  line-height: @layout-header-height;
11
+  vertical-align: top;
12
+  cursor: pointer;
13
+  img {
14
+    display: inline-block;
15
+    vertical-align: middle;
16
+  }
17
+}
18
+
19
+.menu {
20
+  :global(.anticon) {
21
+    margin-right: 8px;
22
+  }
23
+  :global(.ant-dropdown-menu-item) {
24
+    min-width: 160px;
25
+  }
26
+}
27
+
28
+.trigger {
29
+  height: @layout-header-height;
30
+  padding: ~'calc((@{layout-header-height} - 20px) / 2)' 24px;
31
+  font-size: 20px;
32
+  cursor: pointer;
33
+  transition: all 0.3s, padding 0s;
34
+  &:hover {
35
+    background: @pro-header-hover-bg;
36
+  }
37
+}
38
+
39
+.right {
40
+  float: right;
41
+  height: 100%;
42
+  overflow: hidden;
43
+  .action {
44
+    display: inline-block;
45
+    height: 100%;
46
+    padding: 0 12px;
47
+    cursor: pointer;
48
+    transition: all 0.3s;
49
+    > i {
50
+      color: @text-color;
51
+      vertical-align: middle;
52
+    }
53
+    &:hover {
54
+      background: @pro-header-hover-bg;
55
+    }
56
+    &:global(.opened) {
57
+      background: @pro-header-hover-bg;
58
+    }
59
+  }
60
+  .search {
61
+    padding: 0 12px;
62
+    &:hover {
63
+      background: transparent;
64
+    }
65
+  }
66
+  .account {
67
+    .avatar {
68
+      margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
69
+      margin-right: 8px;
70
+      color: @primary-color;
71
+      vertical-align: top;
72
+      background: rgba(255, 255, 255, 0.85);
73
+    }
74
+  }
75
+}
76
+
77
+.dark {
78
+  height: @layout-header-height;
79
+  .action {
80
+    color: rgba(255, 255, 255, 0.85);
81
+    > i {
82
+      color: rgba(255, 255, 255, 0.85);
83
+    }
84
+    &:hover,
85
+    &:global(.opened) {
86
+      background: @primary-color;
87
+    }
88
+    :global(.ant-badge) {
89
+      color: rgba(255, 255, 255, 0.85);
90
+    }
91
+  }
92
+}
93
+
94
+:global(.ant-pro-global-header) {
95
+  .dark {
96
+    .action {
97
+      color: @text-color;
98
+      > i {
99
+        color: @text-color;
100
+      }
101
+      &:hover {
102
+        color: rgba(255, 255, 255, 0.85);
103
+        > i {
104
+          color: rgba(255, 255, 255, 0.85);
105
+        }
106
+      }
107
+    }
108
+  }
109
+}
110
+
111
+@media only screen and (max-width: @screen-md) {
112
+  :global(.ant-divider-vertical) {
113
+    vertical-align: unset;
114
+  }
115
+  .name {
116
+    display: none;
117
+  }
118
+  i.trigger {
119
+    padding: 22px 12px;
120
+  }
121
+  .logo {
122
+    position: relative;
123
+    padding-right: 12px;
124
+    padding-left: 12px;
125
+  }
126
+  .right {
127
+    position: absolute;
128
+    top: 0;
129
+    right: 12px;
130
+    .account {
131
+      .avatar {
132
+        margin-right: 0;
133
+      }
134
+    }
135
+  }
136
+}

+ 10
- 0
src/components/HeaderDropdown/index.jsx Ver arquivo

@@ -0,0 +1,10 @@
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
+  <Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
8
+);
9
+
10
+export default HeaderDropdown;

+ 16
- 0
src/components/HeaderDropdown/index.less Ver arquivo

@@ -0,0 +1,16 @@
1
+@import '~antd/es/style/themes/default.less';
2
+
3
+.container > * {
4
+  background-color: #fff;
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
+}

+ 132
- 0
src/components/HeaderSearch/index.jsx Ver arquivo

@@ -0,0 +1,132 @@
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: '',
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
+      value: '',
84
+    });
85
+  };
86
+  debouncePressEnter = () => {
87
+    const { onPressEnter } = this.props;
88
+    const { value } = this.state;
89
+    onPressEnter(value);
90
+  };
91
+
92
+  render() {
93
+    const { className, placeholder, open, ...restProps } = this.props;
94
+    const { searchMode, value } = this.state;
95
+    delete restProps.defaultOpen; // for rc-select not affected
96
+
97
+    const inputClass = classNames(styles.input, {
98
+      [styles.show]: searchMode,
99
+    });
100
+    return (
101
+      <span
102
+        className={classNames(className, styles.headerSearch)}
103
+        onClick={this.enterSearchMode}
104
+        onTransitionEnd={({ propertyName }) => {
105
+          if (propertyName === 'width' && !searchMode) {
106
+            const { onVisibleChange } = this.props;
107
+            onVisibleChange(searchMode);
108
+          }
109
+        }}
110
+      >
111
+        <Icon type="search" key="Icon" />
112
+        <AutoComplete
113
+          key="AutoComplete"
114
+          {...restProps}
115
+          className={inputClass}
116
+          value={value}
117
+          onChange={this.onChange}
118
+        >
119
+          <Input
120
+            ref={node => {
121
+              this.inputRef = node;
122
+            }}
123
+            aria-label={placeholder}
124
+            placeholder={placeholder}
125
+            onKeyDown={this.onKeyDown}
126
+            onBlur={this.leaveSearchMode}
127
+          />
128
+        </AutoComplete>
129
+      </span>
130
+    );
131
+  }
132
+}

+ 32
- 0
src/components/HeaderSearch/index.less Ver arquivo

@@ -0,0 +1,32 @@
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
+}

+ 95
- 0
src/components/NoticeIcon/NoticeList.jsx Ver arquivo

@@ -0,0 +1,95 @@
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
+      <div className={styles.notFound}>
21
+        <img
22
+          src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
23
+          alt="not found"
24
+        />
25
+        <div>{emptyText}</div>
26
+      </div>
27
+    );
28
+  }
29
+
30
+  return (
31
+    <div>
32
+      <List
33
+        className={styles.list}
34
+        dataSource={data}
35
+        renderItem={(item, i) => {
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
+              <Avatar className={styles.avatar} src={item.avatar} />
43
+            ) : (
44
+              <span className={styles.iconElement}>{item.avatar}</span>
45
+            )
46
+          ) : null;
47
+          return (
48
+            <List.Item
49
+              className={itemCls}
50
+              key={item.key || i}
51
+              onClick={() => onClick && onClick(item)}
52
+            >
53
+              <List.Item.Meta
54
+                className={styles.meta}
55
+                avatar={leftIcon}
56
+                title={
57
+                  <div className={styles.title}>
58
+                    {item.title}
59
+                    <div className={styles.extra}>{item.extra}</div>
60
+                  </div>
61
+                }
62
+                description={
63
+                  <div>
64
+                    <div className={styles.description}>{item.description}</div>
65
+                    <div className={styles.datetime}>{item.datetime}</div>
66
+                  </div>
67
+                }
68
+              />
69
+            </List.Item>
70
+          );
71
+        }}
72
+      />
73
+      <div className={styles.bottomBar}>
74
+        {showClear ? (
75
+          <div onClick={onClear}>
76
+            {clearText} {title}
77
+          </div>
78
+        ) : null}
79
+        {showViewMore ? (
80
+          <div
81
+            onClick={e => {
82
+              if (onViewMore) {
83
+                onViewMore(e);
84
+              }
85
+            }}
86
+          >
87
+            {viewMoreText}
88
+          </div>
89
+        ) : null}
90
+      </div>
91
+    </div>
92
+  );
93
+};
94
+
95
+export default NoticeList;

+ 105
- 0
src/components/NoticeIcon/NoticeList.less Ver arquivo

@@ -0,0 +1,105 @@
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
+    &:hover {
96
+      color: @heading-color;
97
+    }
98
+    &:only-child {
99
+      width: 100%;
100
+    }
101
+    &:not(:only-child):last-child {
102
+      border-left: 1px solid @border-color-split;
103
+    }
104
+  }
105
+}

+ 155
- 0
src/components/NoticeIcon/index.jsx Ver arquivo

@@ -0,0 +1,155 @@
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
+        <TabPane tab={tabTitle} key={title}>
70
+          <NoticeList
71
+            clearText={clearText}
72
+            viewMoreText={viewMoreText}
73
+            data={list}
74
+            onClear={() => 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
+        </TabPane>
83
+      );
84
+    });
85
+    return (
86
+      <>
87
+        <Spin spinning={loading} delay={300}>
88
+          <Tabs className={styles.tabs} onChange={this.onTabChange}>
89
+            {panes}
90
+          </Tabs>
91
+        </Spin>
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 || <Icon type="bell" className={styles.icon} />;
113
+    const trigger = (
114
+      <span
115
+        className={classNames(noticeButtonClass, {
116
+          opened: visible,
117
+        })}
118
+      >
119
+        <Badge
120
+          count={count}
121
+          style={{
122
+            boxShadow: 'none',
123
+          }}
124
+          className={styles.badge}
125
+        >
126
+          {NoticeBellIcon}
127
+        </Badge>
128
+      </span>
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
+      <HeaderDropdown
143
+        placement="bottomRight"
144
+        overlay={notificationBox}
145
+        overlayClassName={styles.popover}
146
+        trigger={['click']}
147
+        visible={visible}
148
+        onVisibleChange={this.handleVisibleChange}
149
+        {...popoverProps}
150
+      >
151
+        {trigger}
152
+      </HeaderDropdown>
153
+    );
154
+  }
155
+}

+ 31
- 0
src/components/NoticeIcon/index.less Ver arquivo

@@ -0,0 +1,31 @@
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
+}

+ 16
- 0
src/components/PageLoading/index.jsx Ver arquivo

@@ -0,0 +1,16 @@
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
+  <div
7
+    style={{
8
+      paddingTop: 100,
9
+      textAlign: 'center',
10
+    }}
11
+  >
12
+    <Spin size="large" />
13
+  </div>
14
+);
15
+
16
+export default PageLoading;

+ 53
- 0
src/components/SelectLang/index.jsx Ver arquivo

@@ -0,0 +1,53 @@
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, false);
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
+    <Menu className={styles.menu} selectedKeys={[selectedLang]} onClick={changeLang}>
29
+      {locales.map(locale => (
30
+        <Menu.Item key={locale}>
31
+          <span role="img" aria-label={languageLabels[locale]}>
32
+            {languageIcons[locale]}
33
+          </span>{' '}
34
+          {languageLabels[locale]}
35
+        </Menu.Item>
36
+      ))}
37
+    </Menu>
38
+  );
39
+  return (
40
+    <HeaderDropdown overlay={langMenu} placement="bottomRight">
41
+      <span className={classNames(styles.dropDown, className)}>
42
+        <Icon
43
+          type="global"
44
+          title={formatMessage({
45
+            id: 'navBar.lang',
46
+          })}
47
+        />
48
+      </span>
49
+    </HeaderDropdown>
50
+  );
51
+};
52
+
53
+export default SelectLang;

+ 24
- 0
src/components/SelectLang/index.less Ver arquivo

@@ -0,0 +1,24 @@
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
+}

+ 33
- 0
src/components/SettingDrawer/themeColorClient.js Ver arquivo

@@ -0,0 +1,33 @@
1
+// eslint-disable-next-line eslint-comments/disable-enable-pair
2
+
3
+/* eslint-disable import/no-extraneous-dependencies */
4
+import client from 'webpack-theme-color-replacer/client';
5
+import generate from '@ant-design/colors/lib/generate';
6
+export default {
7
+  getAntdSerials(color) {
8
+    const lightCount = 9;
9
+    const divide = 10; // 淡化(即less的tint)
10
+
11
+    let lightens = new Array(lightCount).fill(0);
12
+    lightens = lightens.map((_, i) => client.varyColor.lighten(color, i / divide));
13
+    const colorPalettes = generate(color);
14
+    return lightens.concat(colorPalettes);
15
+  },
16
+
17
+  changeColor(color) {
18
+    if (!color) {
19
+      return Promise.resolve();
20
+    }
21
+
22
+    const options = {
23
+      // new colors array, one-to-one corresponde with `matchColors`
24
+      newColors: this.getAntdSerials(color),
25
+
26
+      changeUrl(cssUrl) {
27
+        // while router is not `hash` mode, it needs absolute path
28
+        return `/${cssUrl}`;
29
+      },
30
+    };
31
+    return client.changer.changeColor(options, Promise);
32
+  },
33
+};

+ 1
- 0
src/e2e/__mocks__/antd-pro-merge-less.js Ver arquivo

@@ -0,0 +1 @@
1
+export default undefined;

+ 39
- 0
src/e2e/baseLayout.e2e.js Ver arquivo

@@ -0,0 +1,39 @@
1
+const RouterConfig = require('../../config/config').default.routes;
2
+const { uniq } = require('lodash');
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
+describe('Ant Design Pro E2E test', () => {
23
+  const testPage = path => async () => {
24
+    await page.goto(`${BASE_URL}${path}`);
25
+    await page.waitForSelector('footer', {
26
+      timeout: 2000,
27
+    });
28
+    const haveFooter = await page.evaluate(
29
+      () => document.getElementsByTagName('footer').length > 0,
30
+    );
31
+    expect(haveFooter).toBeTruthy();
32
+  };
33
+
34
+  const routers = formatter(RouterConfig);
35
+  console.log('routers', routers);
36
+  routers.forEach(route => {
37
+    it(`test pages ${route}`, testPage(route));
38
+  });
39
+});

+ 15
- 0
src/e2e/topMenu.e2e.js Ver arquivo

@@ -0,0 +1,15 @@
1
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
2
+
3
+describe('Homepage', () => {
4
+  it('topmenu should have footer', async () => {
5
+    const params = '/form/basic-form?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
+});

+ 101
- 0
src/global.jsx Ver arquivo

@@ -0,0 +1,101 @@
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
+      <Button
55
+        type="primary"
56
+        onClick={() => {
57
+          notification.close(key);
58
+          reloadSW();
59
+        }}
60
+      >
61
+        {formatMessage({
62
+          id: 'app.pwa.serviceworker.updated.ok',
63
+        })}
64
+      </Button>
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
+}

+ 61
- 0
src/global.less Ver arquivo

@@ -0,0 +1,61 @@
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
+
49
+.ant-pro-top-nav-header.light {
50
+  background:rgba(2,134,213,1);
51
+}
52
+
53
+.ant-pro-top-nav-header-menu .ant-menu {
54
+  display: none;
55
+}
56
+
57
+.antd-pro-components-global-header-index-right,
58
+.ant-pro-top-nav-header.light h1
59
+{
60
+  color: #fff;
61
+}

+ 129
- 0
src/layouts/BasicLayout.jsx Ver arquivo

@@ -0,0 +1,129 @@
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 from '@ant-design/pro-layout';
7
+import React, { useEffect } from 'react';
8
+import Link from 'umi/link';
9
+import { connect } from 'dva';
10
+import { formatMessage } from 'umi-plugin-react/locale';
11
+import Authorized from '@/utils/Authorized';
12
+import RightContent from '@/components/GlobalHeader/RightContent';
13
+import { isAntDesignPro } from '@/utils/utils';
14
+import logo from '../assets/logo.svg';
15
+
16
+/**
17
+ * use Authorized check all menu item
18
+ */
19
+const menuDataRender = menuList =>
20
+  menuList.map(item => {
21
+    const localItem = { ...item, children: item.children ? menuDataRender(item.children) : [] };
22
+    return Authorized.check(item.authority, localItem, null);
23
+  });
24
+
25
+const footerRender = (_, defaultDom) => {
26
+  // if (!isAntDesignPro()) {
27
+  //   return defaultDom;
28
+  // }
29
+
30
+  // return (
31
+  //   <>
32
+  //     {defaultDom}
33
+  //     <div
34
+  //       style={{
35
+  //         padding: '0px 24px 24px',
36
+  //         textAlign: 'center',
37
+  //       }}
38
+  //     >
39
+  //       <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">
40
+  //         <img
41
+  //           src="https://www.netlify.com/img/global/badges/netlify-color-bg.svg"
42
+  //           width="82px"
43
+  //           alt="netlify logo"
44
+  //         />
45
+  //       </a>
46
+  //     </div>
47
+  //   </>
48
+  // );
49
+  return (
50
+    <footer className="ant-pro-global-footer">
51
+      <div className="ant-pro-global-footer-copyright">Copyright @ 营销云</div>
52
+    </footer>
53
+  )
54
+};
55
+
56
+const BasicLayout = props => {
57
+  const { dispatch, children, settings } = props;
58
+  /**
59
+   * constructor
60
+   */
61
+
62
+  useEffect(() => {
63
+    if (dispatch) {
64
+      dispatch({
65
+        type: 'user/fetchCurrent',
66
+      });
67
+      dispatch({
68
+        type: 'settings/getSetting',
69
+      });
70
+    }
71
+  }, []);
72
+  /**
73
+   * init variables
74
+   */
75
+
76
+  const handleMenuCollapse = payload => {
77
+    if (dispatch) {
78
+      dispatch({
79
+        type: 'global/changeLayoutCollapsed',
80
+        payload,
81
+      });
82
+    }
83
+  };
84
+
85
+  return (
86
+    <ProLayout
87
+      logo={logo}
88
+      onCollapse={handleMenuCollapse}
89
+      menuItemRender={(menuItemProps, defaultDom) => {
90
+        if (menuItemProps.isUrl) {
91
+          return defaultDom;
92
+        }
93
+
94
+        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
95
+      }}
96
+      breadcrumbRender={(routers = []) => [
97
+        {
98
+          path: '/',
99
+          breadcrumbName: formatMessage({
100
+            id: 'menu.home',
101
+            defaultMessage: 'Home',
102
+          }),
103
+        },
104
+        ...routers,
105
+      ]}
106
+      itemRender={(route, params, routes, paths) => {
107
+        const first = routes.indexOf(route) === 0;
108
+        return first ? (
109
+          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
110
+        ) : (
111
+          <span>{route.breadcrumbName}</span>
112
+        );
113
+      }}
114
+      footerRender={footerRender}
115
+      menuDataRender={menuDataRender}
116
+      formatMessage={formatMessage}
117
+      rightContentRender={rightProps => <RightContent {...rightProps} />}
118
+      {...props}
119
+      {...settings}
120
+    >
121
+      {children}
122
+    </ProLayout>
123
+  );
124
+};
125
+
126
+export default connect(({ global, settings }) => ({
127
+  collapsed: global.collapsed,
128
+  settings,
129
+}))(BasicLayout);

+ 5
- 0
src/layouts/BlankLayout.jsx Ver arquivo

@@ -0,0 +1,5 @@
1
+import React from 'react';
2
+
3
+const Layout = ({ children }) => <div>{children}</div>;
4
+
5
+export default Layout;

+ 43
- 0
src/layouts/SecurityLayout.jsx Ver arquivo

@@ -0,0 +1,43 @@
1
+import React from 'react';
2
+import { connect } from 'dva';
3
+import { Redirect } from 'umi';
4
+import PageLoading from '@/components/PageLoading';
5
+
6
+class SecurityLayout extends React.Component {
7
+  state = {
8
+    isReady: false,
9
+  };
10
+
11
+  componentDidMount() {
12
+    this.setState({
13
+      isReady: true,
14
+    });
15
+    const { dispatch } = this.props;
16
+
17
+    if (dispatch) {
18
+      dispatch({
19
+        type: 'user/fetchCurrent',
20
+      });
21
+    }
22
+  }
23
+
24
+  render() {
25
+    const { isReady } = this.state;
26
+    const { children, loading, currentUser } = this.props;
27
+
28
+    if ((!currentUser.userid && loading) || !isReady) {
29
+      return <PageLoading />;
30
+    }
31
+
32
+    if (!currentUser.userid) {
33
+      return <Redirect to="/user/login"></Redirect>;
34
+    }
35
+
36
+    return children;
37
+  }
38
+}
39
+
40
+export default connect(({ user, loading }) => ({
41
+  currentUser: user.currentUser,
42
+  loading: loading.models.user,
43
+}))(SecurityLayout);

+ 43
- 0
src/layouts/UserLayout.jsx Ver arquivo

@@ -0,0 +1,43 @@
1
+import { DefaultFooter, getMenuData, getPageTitle } from '@ant-design/pro-layout';
2
+import DocumentTitle from 'react-document-title';
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
+  return (
26
+    <DocumentTitle
27
+      title={getPageTitle({
28
+        pathname: location.pathname,
29
+        breadcrumb,
30
+        formatMessage,
31
+        ...props,
32
+      })}
33
+    >
34
+      <div className={styles.container}>
35
+        <div className={styles.content}>
36
+          {children}
37
+        </div>
38
+      </div>
39
+    </DocumentTitle>
40
+  );
41
+};
42
+
43
+export default connect(({ settings }) => ({ ...settings }))(UserLayout);

+ 72
- 0
src/layouts/UserLayout.less Ver arquivo

@@ -0,0 +1,72 @@
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: url('../assets/login-bg.png');
9
+  background-size: 100% 100%;
10
+}
11
+
12
+.lang {
13
+  width: 100%;
14
+  height: 40px;
15
+  line-height: 44px;
16
+  text-align: right;
17
+  :global(.ant-dropdown-trigger) {
18
+    margin-right: 24px;
19
+  }
20
+}
21
+
22
+.content {
23
+  flex: 1;
24
+  padding: 32px 0;
25
+}
26
+
27
+@media (min-width: @screen-md-min) {
28
+  .container {
29
+    background-image: url('../assets/login-bg.png');
30
+    background-repeat: no-repeat;
31
+    // background-position: center 110px;
32
+    background-size: 100% 100%;
33
+  }
34
+
35
+  .content {
36
+    padding: 32px 0 24px;
37
+  }
38
+}
39
+
40
+.top {
41
+  text-align: center;
42
+}
43
+
44
+.header {
45
+  height: 44px;
46
+  line-height: 44px;
47
+  a {
48
+    text-decoration: none;
49
+  }
50
+}
51
+
52
+.logo {
53
+  height: 44px;
54
+  margin-right: 16px;
55
+  vertical-align: top;
56
+}
57
+
58
+.title {
59
+  position: relative;
60
+  top: 2px;
61
+  color: @heading-color;
62
+  font-weight: 600;
63
+  font-size: 33px;
64
+  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
65
+}
66
+
67
+.desc {
68
+  margin-top: 12px;
69
+  margin-bottom: 40px;
70
+  color: @text-color-secondary;
71
+  font-size: @font-size-base;
72
+}

+ 21
- 0
src/locales/en-US.js Ver arquivo

@@ -0,0 +1,21 @@
1
+import component from './en-US/component';
2
+import globalHeader from './en-US/globalHeader';
3
+import menu from './en-US/menu';
4
+import pwa from './en-US/pwa';
5
+import settingDrawer from './en-US/settingDrawer';
6
+import settings from './en-US/settings';
7
+export default {
8
+  'navBar.lang': 'Languages',
9
+  'layout.user.link.help': 'Help',
10
+  'layout.user.link.privacy': 'Privacy',
11
+  'layout.user.link.terms': 'Terms',
12
+  'app.preview.down.block': 'Download this page to your local project',
13
+  'app.welcome.link.fetch-blocks': 'Get all block',
14
+  'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
15
+  ...globalHeader,
16
+  ...menu,
17
+  ...settingDrawer,
18
+  ...settings,
19
+  ...pwa,
20
+  ...component,
21
+};

+ 5
- 0
src/locales/en-US/component.js Ver arquivo

@@ -0,0 +1,5 @@
1
+export default {
2
+  'component.tagSelect.expand': 'Expand',
3
+  'component.tagSelect.collapse': 'Collapse',
4
+  'component.tagSelect.all': 'All',
5
+};

+ 17
- 0
src/locales/en-US/globalHeader.js Ver arquivo

@@ -0,0 +1,17 @@
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
+};

+ 50
- 0
src/locales/en-US/menu.js Ver arquivo

@@ -0,0 +1,50 @@
1
+export default {
2
+  'menu.welcome': 'Welcome',
3
+  'menu.more-blocks': 'More Blocks',
4
+  'menu.home': 'Home',
5
+  'menu.login': 'Login',
6
+  'menu.register': 'Register',
7
+  'menu.register.result': 'Register Result',
8
+  'menu.dashboard': 'Dashboard',
9
+  'menu.dashboard.analysis': 'Analysis',
10
+  'menu.dashboard.monitor': 'Monitor',
11
+  'menu.dashboard.workplace': 'Workplace',
12
+  'menu.exception.403': '403',
13
+  'menu.exception.404': '404',
14
+  'menu.exception.500': '500',
15
+  'menu.form': 'Form',
16
+  'menu.form.basic-form': 'Basic Form',
17
+  'menu.form.step-form': 'Step Form',
18
+  'menu.form.step-form.info': 'Step Form(write transfer information)',
19
+  'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
20
+  'menu.form.step-form.result': 'Step Form(finished)',
21
+  'menu.form.advanced-form': 'Advanced Form',
22
+  'menu.list': 'List',
23
+  'menu.list.table-list': 'Search Table',
24
+  'menu.list.basic-list': 'Basic List',
25
+  'menu.list.card-list': 'Card List',
26
+  'menu.list.search-list': 'Search List',
27
+  'menu.list.search-list.articles': 'Search List(articles)',
28
+  'menu.list.search-list.projects': 'Search List(projects)',
29
+  'menu.list.search-list.applications': 'Search List(applications)',
30
+  'menu.profile': 'Profile',
31
+  'menu.profile.basic': 'Basic Profile',
32
+  'menu.profile.advanced': 'Advanced Profile',
33
+  'menu.result': 'Result',
34
+  'menu.result.success': 'Success',
35
+  'menu.result.fail': 'Fail',
36
+  'menu.exception': 'Exception',
37
+  'menu.exception.not-permission': '403',
38
+  'menu.exception.not-find': '404',
39
+  'menu.exception.server-error': '500',
40
+  'menu.exception.trigger': 'Trigger',
41
+  'menu.account': 'Account',
42
+  'menu.account.center': 'Account Center',
43
+  'menu.account.settings': 'Account Settings',
44
+  'menu.account.trigger': 'Trigger Error',
45
+  'menu.account.logout': 'Logout',
46
+  'menu.editor': 'Graphic Editor',
47
+  'menu.editor.flow': 'Flow Editor',
48
+  'menu.editor.mind': 'Mind Editor',
49
+  'menu.editor.koni': 'Koni Editor',
50
+};

+ 6
- 0
src/locales/en-US/pwa.js Ver arquivo

@@ -0,0 +1,6 @@
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
+};

+ 31
- 0
src/locales/en-US/settingDrawer.js Ver arquivo

@@ -0,0 +1,31 @@
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
+};

+ 60
- 0
src/locales/en-US/settings.js Ver arquivo

@@ -0,0 +1,60 @@
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
+};

+ 19
- 0
src/locales/pt-BR.js Ver arquivo

@@ -0,0 +1,19 @@
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
+};

+ 5
- 0
src/locales/pt-BR/component.js Ver arquivo

@@ -0,0 +1,5 @@
1
+export default {
2
+  'component.tagSelect.expand': 'Expandir',
3
+  'component.tagSelect.collapse': 'Diminuir',
4
+  'component.tagSelect.all': 'Todas',
5
+};

+ 18
- 0
src/locales/pt-BR/globalHeader.js Ver arquivo

@@ -0,0 +1,18 @@
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
+};

+ 50
- 0
src/locales/pt-BR/menu.js Ver arquivo

@@ -0,0 +1,50 @@
1
+export default {
2
+  'menu.welcome': 'Welcome',
3
+  'menu.more-blocks': 'More Blocks',
4
+  'menu.home': 'Início',
5
+  'menu.login': 'Login',
6
+  'menu.register': 'Registro',
7
+  'menu.register.result': 'Resultado de registro',
8
+  'menu.dashboard': 'Dashboard',
9
+  'menu.dashboard.analysis': 'Análise',
10
+  'menu.dashboard.monitor': 'Monitor',
11
+  'menu.dashboard.workplace': 'Ambiente de Trabalho',
12
+  'menu.exception.403': '403',
13
+  'menu.exception.404': '404',
14
+  'menu.exception.500': '500',
15
+  'menu.form': 'Formulário',
16
+  'menu.form.basic-form': 'Formulário Básico',
17
+  'menu.form.step-form': 'Formulário Assistido',
18
+  'menu.form.step-form.info': 'Formulário Assistido(gravar informações de transferência)',
19
+  'menu.form.step-form.confirm': 'Formulário Assistido(confirmar informações de transferência)',
20
+  'menu.form.step-form.result': 'Formulário Assistido(finalizado)',
21
+  'menu.form.advanced-form': 'Formulário Avançado',
22
+  'menu.list': 'Lista',
23
+  'menu.list.table-list': 'Tabela de Busca',
24
+  'menu.list.basic-list': 'Lista Básica',
25
+  'menu.list.card-list': 'Lista de Card',
26
+  'menu.list.search-list': 'Lista de Busca',
27
+  'menu.list.search-list.articles': 'Lista de Busca(artigos)',
28
+  'menu.list.search-list.projects': 'Lista de Busca(projetos)',
29
+  'menu.list.search-list.applications': 'Lista de Busca(aplicações)',
30
+  'menu.profile': 'Perfil',
31
+  'menu.profile.basic': 'Perfil Básico',
32
+  'menu.profile.advanced': 'Perfil Avançado',
33
+  'menu.result': 'Resultado',
34
+  'menu.result.success': 'Sucesso',
35
+  'menu.result.fail': 'Falha',
36
+  'menu.exception': 'Exceção',
37
+  'menu.exception.not-permission': '403',
38
+  'menu.exception.not-find': '404',
39
+  'menu.exception.server-error': '500',
40
+  'menu.exception.trigger': 'Disparar',
41
+  'menu.account': 'Conta',
42
+  'menu.account.center': 'Central da Conta',
43
+  'menu.account.settings': 'Configurar Conta',
44
+  'menu.account.trigger': 'Disparar Erro',
45
+  'menu.account.logout': 'Sair',
46
+  'menu.editor': 'Graphic Editor',
47
+  'menu.editor.flow': 'Flow Editor',
48
+  'menu.editor.mind': 'Mind Editor',
49
+  'menu.editor.koni': 'Koni Editor',
50
+};

+ 7
- 0
src/locales/pt-BR/pwa.js Ver arquivo

@@ -0,0 +1,7 @@
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
+};

+ 32
- 0
src/locales/pt-BR/settingDrawer.js Ver arquivo

@@ -0,0 +1,32 @@
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
+};

+ 60
- 0
src/locales/pt-BR/settings.js Ver arquivo

@@ -0,0 +1,60 @@
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
+};

+ 21
- 0
src/locales/zh-CN.js Ver arquivo

@@ -0,0 +1,21 @@
1
+import component from './zh-CN/component';
2
+import globalHeader from './zh-CN/globalHeader';
3
+import menu from './zh-CN/menu';
4
+import pwa from './zh-CN/pwa';
5
+import settingDrawer from './zh-CN/settingDrawer';
6
+import settings from './zh-CN/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.link.fetch-blocks': '获取全部区块',
14
+  'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
15
+  ...globalHeader,
16
+  ...menu,
17
+  ...settingDrawer,
18
+  ...settings,
19
+  ...pwa,
20
+  ...component,
21
+};

+ 5
- 0
src/locales/zh-CN/component.js Ver arquivo

@@ -0,0 +1,5 @@
1
+export default {
2
+  'component.tagSelect.expand': '展开',
3
+  'component.tagSelect.collapse': '收起',
4
+  'component.tagSelect.all': '全部',
5
+};

+ 17
- 0
src/locales/zh-CN/globalHeader.js Ver arquivo

@@ -0,0 +1,17 @@
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
+};

+ 50
- 0
src/locales/zh-CN/menu.js Ver arquivo

@@ -0,0 +1,50 @@
1
+export default {
2
+  'menu.welcome': '欢迎',
3
+  'menu.more-blocks': '更多区块',
4
+  'menu.home': '首页',
5
+  'menu.login': '登录',
6
+  'menu.register': '注册',
7
+  'menu.register.result': '注册结果',
8
+  'menu.dashboard': 'Dashboard',
9
+  'menu.dashboard.analysis': '分析页',
10
+  'menu.dashboard.monitor': '监控页',
11
+  'menu.dashboard.workplace': '工作台',
12
+  'menu.exception.403': '403',
13
+  'menu.exception.404': '404',
14
+  'menu.exception.500': '500',
15
+  'menu.form': '表单页',
16
+  'menu.form.basic-form': '基础表单',
17
+  'menu.form.step-form': '分步表单',
18
+  'menu.form.step-form.info': '分步表单(填写转账信息)',
19
+  'menu.form.step-form.confirm': '分步表单(确认转账信息)',
20
+  'menu.form.step-form.result': '分步表单(完成)',
21
+  'menu.form.advanced-form': '高级表单',
22
+  'menu.list': '列表页',
23
+  'menu.list.table-list': '查询表格',
24
+  'menu.list.basic-list': '标准列表',
25
+  'menu.list.card-list': '卡片列表',
26
+  'menu.list.search-list': '搜索列表',
27
+  'menu.list.search-list.articles': '搜索列表(文章)',
28
+  'menu.list.search-list.projects': '搜索列表(项目)',
29
+  'menu.list.search-list.applications': '搜索列表(应用)',
30
+  'menu.profile': '详情页',
31
+  'menu.profile.basic': '基础详情页',
32
+  'menu.profile.advanced': '高级详情页',
33
+  'menu.result': '结果页',
34
+  'menu.result.success': '成功页',
35
+  'menu.result.fail': '失败页',
36
+  'menu.exception': '异常页',
37
+  'menu.exception.not-permission': '403',
38
+  'menu.exception.not-find': '404',
39
+  'menu.exception.server-error': '500',
40
+  'menu.exception.trigger': '触发错误',
41
+  'menu.account': '个人页',
42
+  'menu.account.center': '个人中心',
43
+  'menu.account.settings': '个人设置',
44
+  'menu.account.trigger': '触发报错',
45
+  'menu.account.logout': '退出登录',
46
+  'menu.editor': '图形编辑器',
47
+  'menu.editor.flow': '流程编辑器',
48
+  'menu.editor.mind': '脑图编辑器',
49
+  'menu.editor.koni': '拓扑编辑器',
50
+};

+ 6
- 0
src/locales/zh-CN/pwa.js Ver arquivo

@@ -0,0 +1,6 @@
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
+};

+ 31
- 0
src/locales/zh-CN/settingDrawer.js Ver arquivo

@@ -0,0 +1,31 @@
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
+};

+ 55
- 0
src/locales/zh-CN/settings.js Ver arquivo

@@ -0,0 +1,55 @@
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
+};

+ 19
- 0
src/locales/zh-TW.js Ver arquivo

@@ -0,0 +1,19 @@
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
+  ...globalHeader,
14
+  ...menu,
15
+  ...settingDrawer,
16
+  ...settings,
17
+  ...pwa,
18
+  ...component,
19
+};

+ 5
- 0
src/locales/zh-TW/component.js Ver arquivo

@@ -0,0 +1,5 @@
1
+export default {
2
+  'component.tagSelect.expand': '展開',
3
+  'component.tagSelect.collapse': '收起',
4
+  'component.tagSelect.all': '全部',
5
+};

+ 17
- 0
src/locales/zh-TW/globalHeader.js Ver arquivo

@@ -0,0 +1,17 @@
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
+};

+ 50
- 0
src/locales/zh-TW/menu.js Ver arquivo

@@ -0,0 +1,50 @@
1
+export default {
2
+  'menu.welcome': '歡迎',
3
+  'menu.more-blocks': '更多區塊',
4
+  'menu.home': '首頁',
5
+  'menu.login': '登錄',
6
+  'menu.exception.403': '403',
7
+  'menu.exception.404': '404',
8
+  'menu.exception.500': '500',
9
+  'menu.register': '註冊',
10
+  'menu.register.result': '註冊結果',
11
+  'menu.dashboard': 'Dashboard',
12
+  'menu.dashboard.analysis': '分析頁',
13
+  'menu.dashboard.monitor': '監控頁',
14
+  'menu.dashboard.workplace': '工作臺',
15
+  'menu.form': '表單頁',
16
+  'menu.form.basic-form': '基礎表單',
17
+  'menu.form.step-form': '分步表單',
18
+  'menu.form.step-form.info': '分步表單(填寫轉賬信息)',
19
+  'menu.form.step-form.confirm': '分步表單(確認轉賬信息)',
20
+  'menu.form.step-form.result': '分步表單(完成)',
21
+  'menu.form.advanced-form': '高級表單',
22
+  'menu.list': '列表頁',
23
+  'menu.list.table-list': '查詢表格',
24
+  'menu.list.basic-list': '標淮列表',
25
+  'menu.list.card-list': '卡片列表',
26
+  'menu.list.search-list': '搜索列表',
27
+  'menu.list.search-list.articles': '搜索列表(文章)',
28
+  'menu.list.search-list.projects': '搜索列表(項目)',
29
+  'menu.list.search-list.applications': '搜索列表(應用)',
30
+  'menu.profile': '詳情頁',
31
+  'menu.profile.basic': '基礎詳情頁',
32
+  'menu.profile.advanced': '高級詳情頁',
33
+  'menu.result': '結果頁',
34
+  'menu.result.success': '成功頁',
35
+  'menu.result.fail': '失敗頁',
36
+  'menu.account': '個人頁',
37
+  'menu.account.center': '個人中心',
38
+  'menu.account.settings': '個人設置',
39
+  'menu.account.trigger': '觸發報錯',
40
+  'menu.account.logout': '退出登錄',
41
+  'menu.exception': '异常页',
42
+  'menu.exception.not-permission': '403',
43
+  'menu.exception.not-find': '404',
44
+  'menu.exception.server-error': '500',
45
+  'menu.exception.trigger': '触发错误',
46
+  'menu.editor': '圖形編輯器',
47
+  'menu.editor.flow': '流程編輯器',
48
+  'menu.editor.mind': '腦圖編輯器',
49
+  'menu.editor.koni': '拓撲編輯器',
50
+};

+ 6
- 0
src/locales/zh-TW/pwa.js Ver arquivo

@@ -0,0 +1,6 @@
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
+};

+ 31
- 0
src/locales/zh-TW/settingDrawer.js Ver arquivo

@@ -0,0 +1,31 @@
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
+};

+ 55
- 0
src/locales/zh-TW/settings.js Ver arquivo

@@ -0,0 +1,55 @@
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
+};

+ 22
- 0
src/manifest.json Ver arquivo

@@ -0,0 +1,22 @@
1
+{
2
+  "name": "Ant Design Pro",
3
+  "short_name": "Ant Design Pro",
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
+}

+ 114
- 0
src/models/global.js Ver arquivo

@@ -0,0 +1,114 @@
1
+import { queryNotices } from '@/services/user';
2
+const GlobalModel = {
3
+  namespace: 'global',
4
+  state: {
5
+    collapsed: false,
6
+    notices: [],
7
+  },
8
+  effects: {
9
+    *fetchNotices(_, { call, put, select }) {
10
+      const data = yield call(queryNotices);
11
+      yield put({
12
+        type: 'saveNotices',
13
+        payload: data,
14
+      });
15
+      const unreadCount = yield select(
16
+        state => state.global.notices.filter(item => !item.read).length,
17
+      );
18
+      yield put({
19
+        type: 'user/changeNotifyCount',
20
+        payload: {
21
+          totalCount: data.length,
22
+          unreadCount,
23
+        },
24
+      });
25
+    },
26
+
27
+    *clearNotices({ payload }, { put, select }) {
28
+      yield put({
29
+        type: 'saveClearedNotices',
30
+        payload,
31
+      });
32
+      const count = yield select(state => state.global.notices.length);
33
+      const unreadCount = yield select(
34
+        state => state.global.notices.filter(item => !item.read).length,
35
+      );
36
+      yield put({
37
+        type: 'user/changeNotifyCount',
38
+        payload: {
39
+          totalCount: count,
40
+          unreadCount,
41
+        },
42
+      });
43
+    },
44
+
45
+    *changeNoticeReadState({ payload }, { put, select }) {
46
+      const notices = yield select(state =>
47
+        state.global.notices.map(item => {
48
+          const notice = { ...item };
49
+
50
+          if (notice.id === payload) {
51
+            notice.read = true;
52
+          }
53
+
54
+          return notice;
55
+        }),
56
+      );
57
+      yield put({
58
+        type: 'saveNotices',
59
+        payload: notices,
60
+      });
61
+      yield put({
62
+        type: 'user/changeNotifyCount',
63
+        payload: {
64
+          totalCount: notices.length,
65
+          unreadCount: notices.filter(item => !item.read).length,
66
+        },
67
+      });
68
+    },
69
+  },
70
+  reducers: {
71
+    changeLayoutCollapsed(
72
+      state = {
73
+        notices: [],
74
+        collapsed: true,
75
+      },
76
+      { payload },
77
+    ) {
78
+      return { ...state, collapsed: payload };
79
+    },
80
+
81
+    saveNotices(state, { payload }) {
82
+      return {
83
+        collapsed: false,
84
+        ...state,
85
+        notices: payload,
86
+      };
87
+    },
88
+
89
+    saveClearedNotices(
90
+      state = {
91
+        notices: [],
92
+        collapsed: true,
93
+      },
94
+      { payload },
95
+    ) {
96
+      return {
97
+        collapsed: false,
98
+        ...state,
99
+        notices: state.notices.filter(item => item.type !== payload),
100
+      };
101
+    },
102
+  },
103
+  subscriptions: {
104
+    setup({ history }) {
105
+      // Subscribe history(url) change, trigger `load` action if pathname is `/`
106
+      history.listen(({ pathname, search }) => {
107
+        if (typeof window.ga !== 'undefined') {
108
+          window.ga('send', 'pageview', pathname + search);
109
+        }
110
+      });
111
+    },
112
+  },
113
+};
114
+export default GlobalModel;

+ 69
- 0
src/models/login.js Ver arquivo

@@ -0,0 +1,69 @@
1
+import { routerRedux } from 'dva/router';
2
+import { stringify } from 'querystring';
3
+import { fakeAccountLogin, getFakeCaptcha } from '@/services/login';
4
+import { setAuthority } from '@/utils/authority';
5
+import { getPageQuery } from '@/utils/utils';
6
+const Model = {
7
+  namespace: 'login',
8
+  state: {
9
+    status: undefined,
10
+  },
11
+  effects: {
12
+    *login({ payload }, { call, put }) {
13
+      const response = yield call(fakeAccountLogin, payload);
14
+      yield put({
15
+        type: 'changeLoginStatus',
16
+        payload: response,
17
+      }); // Login successfully
18
+
19
+      if (response.status === 'ok') {
20
+        const urlParams = new URL(window.location.href);
21
+        const params = getPageQuery();
22
+        let { redirect } = params;
23
+
24
+        if (redirect) {
25
+          const redirectUrlParams = new URL(redirect);
26
+
27
+          if (redirectUrlParams.origin === urlParams.origin) {
28
+            redirect = redirect.substr(urlParams.origin.length);
29
+
30
+            if (redirect.match(/^\/.*#/)) {
31
+              redirect = redirect.substr(redirect.indexOf('#') + 1);
32
+            }
33
+          } else {
34
+            window.location.href = redirect;
35
+            return;
36
+          }
37
+        }
38
+
39
+        yield put(routerRedux.replace(redirect || '/'));
40
+      }
41
+    },
42
+
43
+    *getCaptcha({ payload }, { call }) {
44
+      yield call(getFakeCaptcha, payload);
45
+    },
46
+
47
+    *logout(_, { put }) {
48
+      const { redirect } = getPageQuery(); // redirect
49
+
50
+      if (window.location.pathname !== '/user/login' && !redirect) {
51
+        yield put(
52
+          routerRedux.replace({
53
+            pathname: '/user/login',
54
+            search: stringify({
55
+              redirect: window.location.href,
56
+            }),
57
+          }),
58
+        );
59
+      }
60
+    },
61
+  },
62
+  reducers: {
63
+    changeLoginStatus(state, { payload }) {
64
+      setAuthority(payload.currentAuthority);
65
+      return { ...state, status: payload.status, type: payload.type };
66
+    },
67
+  },
68
+};
69
+export default Model;

+ 82
- 0
src/models/setting.js Ver arquivo

@@ -0,0 +1,82 @@
1
+import { message } from 'antd';
2
+import defaultSettings from '../../config/defaultSettings';
3
+import themeColorClient from '../components/SettingDrawer/themeColorClient';
4
+
5
+const updateTheme = newPrimaryColor => {
6
+  if (newPrimaryColor) {
7
+    const timeOut = 0;
8
+    const hideMessage = message.loading('正在切换主题!', timeOut);
9
+    themeColorClient.changeColor(newPrimaryColor).finally(() => hideMessage());
10
+  }
11
+};
12
+
13
+const updateColorWeak = colorWeak => {
14
+  const root = document.getElementById('root');
15
+
16
+  if (root) {
17
+    root.className = colorWeak ? 'colorWeak' : '';
18
+  }
19
+};
20
+
21
+const SettingModel = {
22
+  namespace: 'settings',
23
+  state: defaultSettings,
24
+  reducers: {
25
+    getSetting(state = defaultSettings) {
26
+      const setting = {};
27
+      const urlParams = new URL(window.location.href);
28
+      Object.keys(state).forEach(key => {
29
+        if (urlParams.searchParams.has(key)) {
30
+          const value = urlParams.searchParams.get(key);
31
+          setting[key] = value === '1' ? true : value;
32
+        }
33
+      });
34
+      const { primaryColor, colorWeak } = setting;
35
+
36
+      if (primaryColor && state.primaryColor !== primaryColor) {
37
+        updateTheme(primaryColor);
38
+      }
39
+
40
+      updateColorWeak(!!colorWeak);
41
+      return { ...state, ...setting };
42
+    },
43
+
44
+    changeSetting(state = defaultSettings, { payload }) {
45
+      const urlParams = new URL(window.location.href);
46
+      Object.keys(defaultSettings).forEach(key => {
47
+        if (urlParams.searchParams.has(key)) {
48
+          urlParams.searchParams.delete(key);
49
+        }
50
+      });
51
+      Object.keys(payload).forEach(key => {
52
+        if (key === 'collapse') {
53
+          return;
54
+        }
55
+
56
+        let value = payload[key];
57
+
58
+        if (value === true) {
59
+          value = 1;
60
+        }
61
+
62
+        if (defaultSettings[key] !== value) {
63
+          urlParams.searchParams.set(key, value);
64
+        }
65
+      });
66
+      const { primaryColor, colorWeak, contentWidth } = payload;
67
+
68
+      if (primaryColor && state.primaryColor !== primaryColor) {
69
+        updateTheme(primaryColor);
70
+      }
71
+
72
+      if (state.contentWidth !== contentWidth && window.dispatchEvent) {
73
+        window.dispatchEvent(new Event('resize'));
74
+      }
75
+
76
+      updateColorWeak(!!colorWeak);
77
+      window.history.replaceState(null, 'setting', urlParams.href);
78
+      return { ...state, ...payload };
79
+    },
80
+  },
81
+};
82
+export default SettingModel;

+ 46
- 0
src/models/user.js Ver arquivo

@@ -0,0 +1,46 @@
1
+import { queryCurrent, query as queryUsers } from '@/services/user';
2
+const UserModel = {
3
+  namespace: 'user',
4
+  state: {
5
+    currentUser: {},
6
+  },
7
+  effects: {
8
+    *fetch(_, { call, put }) {
9
+      const response = yield call(queryUsers);
10
+      yield put({
11
+        type: 'save',
12
+        payload: response,
13
+      });
14
+    },
15
+
16
+    *fetchCurrent(_, { call, put }) {
17
+      const response = yield call(queryCurrent);
18
+      yield put({
19
+        type: 'saveCurrentUser',
20
+        payload: response,
21
+      });
22
+    },
23
+  },
24
+  reducers: {
25
+    saveCurrentUser(state, action) {
26
+      return { ...state, currentUser: action.payload || {} };
27
+    },
28
+
29
+    changeNotifyCount(
30
+      state = {
31
+        currentUser: {},
32
+      },
33
+      action,
34
+    ) {
35
+      return {
36
+        ...state,
37
+        currentUser: {
38
+          ...state.currentUser,
39
+          notifyCount: action.payload.totalCount,
40
+          unreadCount: action.payload.unreadCount,
41
+        },
42
+      };
43
+    },
44
+  },
45
+};
46
+export default UserModel;

+ 19
- 0
src/pages/404.jsx Ver arquivo

@@ -0,0 +1,19 @@
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
+  <Result
8
+    status="404"
9
+    title="404"
10
+    subTitle="Sorry, the page you visited does not exist."
11
+    extra={
12
+      <Button type="primary" onClick={() => router.push('/')}>
13
+        Back Home
14
+      </Button>
15
+    }
16
+  ></Result>
17
+);
18
+
19
+export default NoFoundPage;

+ 50
- 0
src/pages/Authorized.jsx Ver arquivo

@@ -0,0 +1,50 @@
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
+    // match prefix
11
+    if (pathToRegexp(`${route.path}(.*)`).test(path)) {
12
+      // exact match
13
+      if (route.path === path) {
14
+        authorities = route.authority || authorities;
15
+      } // get children authority recursively
16
+
17
+      if (route.routes) {
18
+        authorities = getRouteAuthority(path, route.routes) || authorities;
19
+      }
20
+    }
21
+  });
22
+  return authorities;
23
+};
24
+
25
+const AuthComponent = ({
26
+  children,
27
+  route = {
28
+    routes: [],
29
+  },
30
+  location = {
31
+    pathname: '',
32
+  },
33
+  user,
34
+}) => {
35
+  const { currentUser } = user;
36
+  const { routes = [] } = route;
37
+  const isLogin = currentUser && currentUser.name;
38
+  return (
39
+    <Authorized
40
+      authority={getRouteAuthority(location.pathname, routes) || ''}
41
+      noMatch={isLogin ? <Redirect to="/exception/403" /> : <Redirect to="/user/login" />}
42
+    >
43
+      {children}
44
+    </Authorized>
45
+  );
46
+};
47
+
48
+export default connect(({ user }) => ({
49
+  user,
50
+}))(AuthComponent);

+ 61
- 0
src/pages/UserManage/index.jsx Ver arquivo

@@ -0,0 +1,61 @@
1
+import React from 'react';
2
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
3
+import { Card, Button, Icon, Tooltip } from 'antd';
4
+import Cell from '../../components/Cell';
5
+import Style from './style.less';
6
+
7
+const { Meta } = Card;
8
+
9
+const lineTextStyle = {
10
+  marginBottom: 0,
11
+  lineHeight: '1.8em'
12
+}
13
+
14
+export default (props) => {
15
+
16
+  const CardBody = (cbProps) => {
17
+    return (
18
+      <>
19
+        <Cell action={
20
+          cbProps.status ?
21
+            (<Button type="link">开启<Icon type="check-circle" /></Button>) :
22
+            (<Button type="link">关闭<Icon type="delete" /></Button>)
23
+          }>
24
+          <h3>南京楼市</h3>
25
+        </Cell>
26
+        <div>
27
+          <p style={lineTextStyle}>联系人 : 曹建芳</p>
28
+          <p style={lineTextStyle}>手机号 : 187 5565 6565</p>
29
+          <p style={lineTextStyle}>小程序 : 南京楼市</p>
30
+          <p style={lineTextStyle}>服务到期时间 : 2019-09-24</p>
31
+        </div>
32
+      </>
33
+    )
34
+  }
35
+
36
+  return (
37
+    <PageHeaderWrapper>
38
+      <div className={Style['flex-box']}>
39
+        <div className={Style['flex-item']}>
40
+          <Card
41
+            style={{width: '560px', boxShadow:'3px 3px 5px #ccc', borderRadius: '12px', overflow: 'hidden'}}
42
+            cover={<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />}
43
+            actions={[
44
+              <Tooltip title="编辑">
45
+                <Icon type="edit" key="edit" />
46
+              </Tooltip>,
47
+              <Tooltip title="权限分配">
48
+                <Icon type="setting" key="rights" />
49
+              </Tooltip>,
50
+              <Tooltip title="重置密码">
51
+                <Icon type="block" key="resetpass" />
52
+              </Tooltip>
53
+            ]}
54
+          >
55
+            <CardBody/>
56
+          </Card>
57
+        </div>
58
+      </div>
59
+    </PageHeaderWrapper>
60
+  )
61
+}

+ 12
- 0
src/pages/UserManage/style.less Ver arquivo

@@ -0,0 +1,12 @@
1
+.flex-box {
2
+  display: flex;
3
+  flex-wrap: wrap;
4
+
5
+  .flex-item {
6
+    flex: none;
7
+  }
8
+
9
+  .flex-auto {
10
+    flex: auto;
11
+  }
12
+}

+ 168
- 0
src/pages/document.ejs Ver arquivo

@@ -0,0 +1,168 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="UTF-8" />
5
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+    <meta
7
+      name="viewport"
8
+      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
9
+    />
10
+    <title>Ant Design Pro</title>
11
+    <link rel="icon" href="/favicon.png" type="image/x-icon" />
12
+  </head>
13
+  <body>
14
+    <noscript>Out-of-the-box mid-stage front/design solution!</noscript>
15
+    <div id="root">
16
+      <style>
17
+        .page-loading-warp {
18
+          padding: 120px;
19
+          display: flex;
20
+          justify-content: center;
21
+          align-items: center;
22
+        }
23
+        .ant-spin {
24
+          -webkit-box-sizing: border-box;
25
+          box-sizing: border-box;
26
+          margin: 0;
27
+          padding: 0;
28
+          color: rgba(0, 0, 0, 0.65);
29
+          font-size: 14px;
30
+          font-variant: tabular-nums;
31
+          line-height: 1.5;
32
+          list-style: none;
33
+          -webkit-font-feature-settings: 'tnum';
34
+          font-feature-settings: 'tnum';
35
+          position: absolute;
36
+          display: none;
37
+          color: #1890ff;
38
+          text-align: center;
39
+          vertical-align: middle;
40
+          opacity: 0;
41
+          -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
42
+          transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
43
+          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
44
+          transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
45
+            -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
46
+        }
47
+
48
+        .ant-spin-spinning {
49
+          position: static;
50
+          display: inline-block;
51
+          opacity: 1;
52
+        }
53
+
54
+        .ant-spin-dot {
55
+          position: relative;
56
+          display: inline-block;
57
+          font-size: 20px;
58
+          width: 20px;
59
+          height: 20px;
60
+        }
61
+
62
+        .ant-spin-dot-item {
63
+          position: absolute;
64
+          display: block;
65
+          width: 9px;
66
+          height: 9px;
67
+          background-color: #1890ff;
68
+          border-radius: 100%;
69
+          -webkit-transform: scale(0.75);
70
+          -ms-transform: scale(0.75);
71
+          transform: scale(0.75);
72
+          -webkit-transform-origin: 50% 50%;
73
+          -ms-transform-origin: 50% 50%;
74
+          transform-origin: 50% 50%;
75
+          opacity: 0.3;
76
+          -webkit-animation: antSpinMove 1s infinite linear alternate;
77
+          animation: antSpinMove 1s infinite linear alternate;
78
+        }
79
+
80
+        .ant-spin-dot-item:nth-child(1) {
81
+          top: 0;
82
+          left: 0;
83
+        }
84
+
85
+        .ant-spin-dot-item:nth-child(2) {
86
+          top: 0;
87
+          right: 0;
88
+          -webkit-animation-delay: 0.4s;
89
+          animation-delay: 0.4s;
90
+        }
91
+
92
+        .ant-spin-dot-item:nth-child(3) {
93
+          right: 0;
94
+          bottom: 0;
95
+          -webkit-animation-delay: 0.8s;
96
+          animation-delay: 0.8s;
97
+        }
98
+
99
+        .ant-spin-dot-item:nth-child(4) {
100
+          bottom: 0;
101
+          left: 0;
102
+          -webkit-animation-delay: 1.2s;
103
+          animation-delay: 1.2s;
104
+        }
105
+
106
+        .ant-spin-dot-spin {
107
+          -webkit-transform: rotate(45deg);
108
+          -ms-transform: rotate(45deg);
109
+          transform: rotate(45deg);
110
+          -webkit-animation: antRotate 1.2s infinite linear;
111
+          animation: antRotate 1.2s infinite linear;
112
+        }
113
+
114
+        .ant-spin-lg .ant-spin-dot {
115
+          font-size: 32px;
116
+          width: 32px;
117
+          height: 32px;
118
+        }
119
+
120
+        .ant-spin-lg .ant-spin-dot i {
121
+          width: 14px;
122
+          height: 14px;
123
+        }
124
+
125
+        @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
126
+          .ant-spin-blur {
127
+            background: #fff;
128
+            opacity: 0.5;
129
+          }
130
+        }
131
+
132
+        @-webkit-keyframes antSpinMove {
133
+          to {
134
+            opacity: 1;
135
+          }
136
+        }
137
+
138
+        @keyframes antSpinMove {
139
+          to {
140
+            opacity: 1;
141
+          }
142
+        }
143
+
144
+        @-webkit-keyframes antRotate {
145
+          to {
146
+            -webkit-transform: rotate(405deg);
147
+            transform: rotate(405deg);
148
+          }
149
+        }
150
+
151
+        @keyframes antRotate {
152
+          to {
153
+            -webkit-transform: rotate(405deg);
154
+            transform: rotate(405deg);
155
+          }
156
+        }
157
+      </style>
158
+      <div class="page-loading-warp">
159
+        <div class="ant-spin ant-spin-lg ant-spin-spinning">
160
+          <span class="ant-spin-dot ant-spin-dot-spin"
161
+            ><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i
162
+            ><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i
163
+          ></span>
164
+        </div>
165
+      </div>
166
+    </div>
167
+  </body>
168
+</html>

+ 0
- 0
src/pages/user/login/components/Login/LoginContext.jsx Ver arquivo


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff