Compare commits

...
126 Commits
Author SHA1 Message Date
zuiidea 5abad21082 ✨ Optimization Split Chunks, Antd Icon, CI. 2020-08-20 14:16:32 +08:00
Baorong Li 1ae41c7ae5 Merge pull request #1060 from mlboy/patch-1
Update README.md
2020-06-03 14:46:44 +08:00
mlboy e41671dbf9 Update README.md
调整antd版本
2020-05-13 07:42:01 +08:00
Baorong Li eaeb9092e4 bump antd-admin@5.3.0 2020-05-10 21:05:23 +08:00
Baorong Li 2f3ca0a006 Merge branch 'develop' 2020-05-10 21:04:40 +08:00
Baorong Li 1b65351be4 upgrade umi@3 fin. 2020-03-24 15:17:57 +08:00
Baorong Li 0422f6d51e upgrade umi@3 partly 2020-03-24 12:12:20 +08:00
Baorong Li 8d050d4040 fix pathToRegexp not work 2020-03-20 11:59:04 +08:00
zuiidea 82b3a4b384 ⚡️ Exclude node_modules by babel-loader. 2020-03-19 22:26:55 +08:00
Baorong Li d2f391a881 upgrade umi@3 partly 2020-03-19 17:06:31 +08:00
Baorong Li 9e778487d9 upgrade umi@3 partly 2020-03-19 12:50:12 +08:00
Baorong Li 7938d71f2f upgrade umi@3 partly 2020-03-19 00:05:44 +08:00
Baorong Li 422a6fed30 upgrade umi@3 partly 2020-03-18 16:17:25 +08:00
Baorong Li c6ea0454b3 upgrade umi@3 partly 2020-03-15 08:24:05 +08:00
Baorong Li 467767a6c5 upgrade umi@3 partly 2020-03-05 23:22:02 +08:00
Baorong Li cf795c512a bump antd-admin@5.2.0 2020-03-05 20:21:22 +08:00
Baorong Li 18b499030d upgrade antd@4.0.0 2020-02-29 22:47:45 +08:00
zuiidea d800d6f3ce Merge branch 'master' into develop 2020-01-08 09:31:45 +08:00
zuiidea 185f706107 💚 Fixing now config. 2020-01-07 17:17:57 +08:00
zuiidea 5a4661cdad Merge branch 'develop' of https://github.com/zuiidea/antd-admin into develop 2020-01-07 16:02:51 +08:00
zuiidea 7abdd94a0e 🚀 Optimize deployment configuration. 2020-01-07 16:02:33 +08:00
Baorong Li 22f0684ab0 chore: docs normal 2020-01-07 15:58:48 +08:00
Baorong Li 80b2c63693 Merge pull request #1040 from vagusX/feat/migrate-antd-v4
feat: migrate to antd v4
2019-12-02 22:07:08 -06:00
vagusX 4b2a553bda feat: add antd v4 dependencies 2019-12-03 10:19:54 +08:00
vagusX ad30e73b10 feat: migrate to antd v4 2019-12-03 09:58:49 +08:00
Baorong Li 15b62e302d bump antd-admin@5.1.3 2019-10-22 10:38:52 +08:00
Baorong Li 9d5ec20f20 Merge branch 'develop' 2019-10-22 10:37:43 +08:00
Baorong Li c878425fd0 upgrade antd@3.24.x 2019-10-22 10:30:04 +08:00
Baorong Li 90e80dda64 Merge pull request #1022 from rioarray/feature/inline-conditional-rendering
inline conditional rendering in some components
2019-10-17 16:32:30 +08:00
rioarray 1db5bfeab5 inline conditional rendering in some components 2019-10-17 14:45:30 +07:00
zuiidea b2b3fcd4a3 ✨ Support disable i18n. 2019-10-09 20:20:11 +08:00
Baorong Li 9cf97375c7 fix 404 when path = '/' 2019-08-01 23:12:42 +08:00
Baorong Li 7839b6affb Merge pull request #1007 from Louis-7/master
[fix] add notification popup to #primaryLayout instead of #layoutHeader
2019-07-25 08:26:35 -05:00
Louis 597064a07d [fix] add notification popup to #primaryLayout instead of #layoutHeader 2019-07-25 11:47:32 +08:00
Baorong Li 0ea57d14c9 fix #980 cannot read property 'map' of null 2019-07-07 23:01:20 +08:00
Baorong Li afd736d615 bump antd-admin@5.1.2 2019-07-07 22:48:34 +08:00
Baorong Li ce9b8c376f Merge pull request #996 from tuantvk/master
🐛 fix bug sub menu Charts when collapsed
2019-07-07 08:03:31 -05:00
tuantv 79776e6752 🐛 fix bug sub menu Charts when collapsed 2019-07-07 11:56:52 +07:00
zuiidea a2432022a1 ✨ Updating manifest logo and adding portugal flag image. 2019-07-02 17:47:16 +08:00
zuiidea ea5ee96bdc 🎨 Improving format of the code. 2019-07-06 11:02:56 +08:00
zuiidea fe954092a4 🐛 Fixing dva history.listen multiple triggers. 2019-07-06 10:50:50 +08:00
zuiidea 5e148fec92 Merge branch 'develop' of https://github.com/zuiidea/antd-admin into develop 2019-07-06 09:43:30 +08:00
Baorong Li 5daa2a9548 add moment locale setting 2019-07-05 22:26:29 +08:00
Baorong Li f1913f7b18 add pt_BR locale-provider in layout 2019-07-05 22:16:54 +08:00
zuiidea 65a453553e ✨ Updating mock avatar image. 2019-07-05 19:52:37 +08:00
Baorong Li 29da8f24da Merge pull request #989 from tuantvk/master
🐛 fix remove and update user error handleRefresh
2019-06-29 09:57:46 -05:00
tuantv 3443ad9968 🐛 fix edit and update user error handleRefresh 2019-06-29 21:19:57 +07:00
Baorong Li 838dc93eff bump antd-admin@5.1.1 2019-06-29 10:56:03 +08:00
Baorong Li 07ef68ec1f Merge remote-tracking branch 'origin/develop' 2019-06-29 10:54:10 +08:00
Baorong Li 3cb7f86086 fix login jump to /enundefined 2019-06-29 10:49:11 +08:00
Baorong Li fc9565c157 add a flag to detect refresh saving query times in model/app.js 2019-06-24 20:50:09 +08:00
Baorong Li 557f6022f3 bump antd-admin@5.1.0 2019-06-24 16:05:28 +08:00
Baorong Li d0a56169e3 revert dispatch query in model/app.js 2019-06-24 15:40:44 +08:00
Baorong Li f7d6e2b4ed Merge pull request #984 from lovemegowin/master
refactor user,menu,permission storage strategy
2019-06-24 15:26:06 +08:00
lovemegowin df82ee42fd fix #985 , close #985 2019-06-24 15:20:25 +08:00
lovemegowin a29d761483 refactor user, menu, permission storage strategy then make full loader unnecessary 2019-06-21 15:26:06 +08:00
lovemegowin 713a86b9d6 add null point assert 2019-06-21 15:23:19 +08:00
lovemegowin d6250f16b0 fullScreen loader is not always necessary refactor default spinning to false 2019-06-21 14:12:15 +08:00
Baorong Li 1c5d5c3788 upgrade umi@2.8.x 2019-06-20 16:31:00 +08:00
Baorong Li 699e9d9ff1 fix warning inlineCollapsed not control Menu under Sider 2019-06-04 15:47:35 +08:00
Baorong Li fad0cb931a use options instead of option in react-perfect-scrollbar 2019-06-04 15:34:45 +08:00
Baorong Li 5d7ff55869 upgrade dependency 2019-06-04 15:09:51 +08:00
Baorong Li 642ccb5b82 Merge pull request #975 from deepred5/master
fix: 关闭弹窗时有动画效果
2019-05-27 23:59:48 -05:00
深红 51a7ce0827 update: 关闭弹窗时销毁子元素,保证initialValue更新并且关闭弹窗时有动画效果 2019-05-28 10:27:27 +08:00
Baorong Li 157a54b11d Merge pull request #969 from ccyanxyz/patch-1
fix markdown code block in docs/layout.md
2019-05-04 18:32:34 -05:00
cyan be083d81fc fix markdown code block 2019-05-05 00:38:43 +08:00
zuiidea f6c869ad47 Merge pull request #965 from zuiidea/develop
✏️  Fixing typos, exlude => exclude.
2019-04-18 10:10:14 +08:00
zuiidea bef5b26509 ✏️ Fixing typos, exlude => exclude. 2019-04-18 09:58:16 +08:00
Baorong Li 59cfe24178 Merge branch 'develop'
# Conflicts:
#	package.json
2019-04-10 07:27:25 +08:00
Baorong Li 87d0d58ae8 Merge commit '0ac4839e948f7b1d34c90d33f4ece483bd9d2f48' 2019-04-10 07:24:20 +08:00
Baorong Li de8b6c972d bump antd-admin@5.0.6 2019-04-10 07:24:06 +08:00
Baorong Li 0ac4839e94 upgrade umi@2.6.x 2019-04-10 07:22:53 +08:00
Baorong Li 565bce6d8b refix #958 by reset form in componentDidUpdate 2019-04-05 09:02:25 +08:00
Baorong Li b278b028cf fix #958 redux form initialValues not updating with state 2019-04-04 16:45:28 +08:00
Baorong Li 33abd9369e fix #957 an flaw ignored 2019-03-19 10:59:37 +08:00
Baorong Li e6b2f7f271 fix #950 modify z-index in header 2019-03-13 16:59:12 +08:00
Baorong Li 0bd1e9cf59 fix #947 misspelled word cancel 2019-03-06 12:11:42 +08:00
Baorong Li 693625bf2c bump antd-admin@5.0.5 2019-03-05 16:23:16 +08:00
Baorong Li 3a6c587569 upgrade antd@3.14 2019-03-05 16:17:12 +08:00
Baorong Li bd85eaf0a0 fix less is not a command in prettier 2019-03-05 16:15:33 +08:00
Baorong Li 29aa0f416e fix Cannot read property 'addEventListener' of null #946 2019-03-05 16:15:02 +08:00
Baorong Li 5b32607911 bump antd-admin@5.0.4 2019-02-19 22:00:36 +08:00
Baorong Li 76e5359752 upgrade umi@2.5.0 2019-02-19 21:46:01 +08:00
Baorong Li 5ec4efbb6e reset default lang to en 2019-02-07 09:19:39 +08:00
Baorong Li f8bb7b421a remove description about support IE9/IE10 that not working well 2019-02-07 08:52:40 +08:00
Baorong Li 887c169aa8 upgrade and lock antd@3.13.0 2019-02-07 08:36:53 +08:00
Baorong Li 8c10dcc6c2 Merge pull request #939 from rrfaria/feature/pt-br
added pt-br language and some improvements to language control to route mocks
2019-02-07 08:08:01 +08:00
Rafael Faria 671da6b5fa added pt-br and some improvements to language control 2019-02-06 14:39:02 -02:00
Baorong Li 7fdb2f4ea8 bump antd-admin@5.0.3 2019-01-11 18:43:48 +08:00
Baorong Li 67388b31f5 enable umi treeShaking 2019-01-11 18:40:39 +08:00
Baorong Li 2379cca765 upgrade umi@2.4.0 & antd@3.12.x 2019-01-11 18:39:19 +08:00
Baorong Li 34be6651e5 fix #935 root path / will be not found after login 2019-01-10 17:36:23 +08:00
Baorong Li 10f8e3e8ef fix mix require / module.exports and import / export #934 2019-01-08 23:30:18 +08:00
Baorong Li 0c6ea8eb5c fix cannot read property 'hash' of undefined by turn off hardSource 2019-01-08 22:56:23 +08:00
Baorong Li 6e69b06e61 Publish antd-admin@5.0.2 2018-12-28 14:01:51 +08:00
Baorong Li 98ba383a68 upgrade umi@2.3 antd@3.11 2018-12-28 11:42:44 +08:00
Baorong Li 4e6d81a9bf Merge pull request #928 from luckcoding/fix-bread-link
fix bread route has no matching lang
2018-12-13 22:35:56 +08:00
Baorong Li 0e2c206023 Merge branch 'develop' into fix-bread-link 2018-12-13 22:35:47 +08:00
luckcoding 285f06d1e7 fix bread route has no matching lang 2018-12-13 16:57:49 +08:00
Baorong Li d03787a871 Publish antd-admin@5.0.1 2018-12-12 13:48:34 +08:00
zuiidea 6aead03f3e 🐛 Fix style error when menu is collapsed. 2018-12-12 11:35:44 +08:00
Baorong Li 0949a298c9 fix #924 404 Not Found after router change 2018-12-12 06:53:26 +08:00
Baorong Li 94c8e2168f fix #926 createUser api in services 2018-12-12 06:17:30 +08:00
Baorong Li 358134f417 fallback umi to 2.2.8 to fix #923 2018-12-09 23:07:16 +08:00
Baorong Li 7eb9e06452 upgrade antd@3.10.9, umi@2.3.0-beta.3 2018-12-01 11:29:09 +08:00
zuiidea fab06d5005 fix: 🐛 CI fails when pull request 2018-11-29 10:00:11 +08:00
Baorong Li 7483572b1d Merge pull request #921 from francolaiuppa/patch-1
Mini typo fixes for English docs, Configuration section.
2018-11-29 09:45:38 +08:00
Franco Laiuppa d1ce8e0739 Mini typo fixes
Hi! Great project!

I was reading the docs and noticed some typos regarding the layout names and the `exclude` parameter, so I've decided to fix it.
I'm new to this repo so I hope this is a valid edit if not please let me know and I'll change it.

Thanks!
2018-11-28 16:53:25 +01:00
zuiidea 16860f93ad Merge pull request #920 from alexxyzeng/alex
fix: show vertical centered modal in ie
2018-11-28 10:18:22 +08:00
xiayao 6ba45d5bc3 fix: show vertical centered modal in ie 2018-11-28 00:44:21 +08:00
zuiidea f230429ddd Merge branch 'develop' of https://github.com/zuiidea/antd-admin into develop 2018-11-21 10:36:51 +08:00
zuiidea 78c3b6d597 📝 Add some comments image to document. 2018-11-20 21:30:22 +08:00
Baorong Li cdbc2782ff chore: remove beta tag in version from package.json 2018-11-20 17:50:20 +08:00
zuiidea 108aca99b9 📝 Add documentation for Add Layout. 2018-11-18 23:08:12 +08:00
Baorong Li 7dd5dfb1bf fix: icons' format not right in manifest.json 2018-11-15 14:11:47 +08:00
zuiidea ee7a0fffc2 ⚡ Update manifest.json. 2018-11-15 00:11:17 +08:00
zuiidea 6999871860 📝 Add documentation for Add Luanguage. 2018-11-14 23:46:59 +08:00
zuiidea 6742207c77 🐛 Fix cannot translate fields with variables. 2018-11-14 22:44:46 +08:00
Baorong Li d71c29ee2e feat: enable pwa 2018-11-14 13:37:34 +08:00
zuiidea 6917d6b89a Merge branch 'develop' of https://github.com/zuiidea/antd-admin into develop 2018-11-12 23:31:11 +08:00
zuiidea c3f369f0b3 📝 Add documentation for API configuration. 2018-11-12 23:30:56 +08:00
Baorong Li 01abc5ca38 upgrade antd@3.10.5 2018-11-10 09:50:20 +08:00
Baorong Li 08a348090c upgrade umi@2.3.0-beta.1 2018-11-09 23:16:42 +08:00
Baorong Li 63b1ecf79f Upgrade umi@2.2.5 2018-11-09 11:26:19 +08:00
Baorong Li efe7e74375 fix #913 handleMenuClick is not defined 2018-11-07 16:23:36 +08:00
Baorong Li 6d552d22d9 add doc about custom http header #829 2018-11-05 11:23:43 +08:00
110 changed files with 2541 additions and 1010 deletions
+13 -18
View File
@@ -1,23 +1,18 @@
language: node_js language: node_js
node_js: node_js:
- node - node
script: script:
- npm run build - npm run build
before_install: before_install:
- | - yarn global add now
if [ "$TRAVIS_BRANCH" = "develop" ]; then
for prefixed_envvar in ${!DEV_*}; do
eval export ${prefixed_envvar#DEV_}="${!prefixed_envvar}"
done
elif [ "$TRAVIS_BRANCH" = "master" ]; then
for prefixed_envvar in ${!PROD_*}; do
eval export ${prefixed_envvar#PROD_}="${!prefixed_envvar}"
done
fi
- openssl aes-256-cbc -K $encrypted_18b2305b78c9_key -iv $encrypted_18b2305b78c9_iv -in config/id_rsa.enc -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa
- echo -e "Host $HOST\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- yarn global add now
after_script: after_script:
- scp -o stricthostkeychecking=no -r ./dist root@$HOST:$BUILD_DIR - |
- cd ./docs && now -A $DOC_NOW_CONFIG -t $NOW_TOKEN && now alias -A $DOC_NOW_CONFIG -t $NOW_TOKEN if [[ $TRAVIS_BRANCH == 'master' ]]; then
echo $PROD_SITE_NOW_CONFIG >> dist/vercel.json && echo $PROD_DOC_NOW_CONFIG >> docs/vercel.json;
else
echo $DEV_SITE_NOW_CONFIG >> dist/vercel.json && echo $DEV_DOC_NOW_CONFIG >> docs/vercel.json;
fi
- cd ./dist
- now -A vercel.json -t $NOW_TOKEN --prod -c
- cd ../docs
- now -A vercel.json -t $NOW_TOKEN --prod -c
+108 -73
View File
@@ -1,87 +1,28 @@
// https://umijs.org/config/ // https://umijs.org/config/
import { resolve } from 'path' import { resolve } from 'path'
import { i18n } from './src/utils/config' const fs = require('fs')
const path = require('path')
const lessToJs = require('less-vars-to-js')
const isDevelopment = process.env.NODE_ENV === 'development'
// how to speed compile: https://umijs.org/guide/boost-compile-speed
export default { export default {
ignoreMomentLocale: true, // IMPORTANT! change next line to yours or delete. And hide in dev
targets: { ie: 9 }, publicPath: isDevelopment ? '/' : 'https://cdn.antd-admin.zuiidea.com/',
plugins: [
[
// https://umijs.org/plugin/umi-plugin-react.html
'umi-plugin-react',
{
dva: { immer: true },
antd: true,
dynamicImport: {
webpackChunkName: true,
loadingComponent: './components/Loader/Loader',
},
routes: {
exclude: [
/model\.(j|t)sx?$/,
/service\.(j|t)sx?$/,
/models\//,
/components\//,
/services\//,
/chart\/Container\.js$/,
/chart\/ECharts\/.+Component\.js$/,
/chart\/ECharts\/.+ComPonent\.js$/,
/chart\/ECharts\/theme\/.+\.js$/,
/chart\/highCharts\/.+Component\.js$/,
/chart\/highCharts\/mapdata\/.+\.js$/,
/chart\/Recharts\/.+Component\.js$/,
/chart\/Recharts\/Container\.js$/,
],
update: routes => {
if (!i18n) return routes
const newRoutes = []
for (const item of routes[0].routes) {
newRoutes.push(item)
if (item.path) {
newRoutes.push(
Object.assign({}, item, {
path:
`/:lang(${i18n.languages
.map(item => item.key)
.join('|')})` + item.path,
})
)
}
}
routes[0].routes = newRoutes
return routes
},
},
dll: {
include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch', 'antd/es'],
},
hardSource: /* isMac */ process.platform === 'darwin',
},
],
],
// Theme for antd
// https://ant.design/docs/react/customize-theme
theme: './config/theme.config.js',
// Webpack Configuration
proxy: {
'/api/v1/weather': {
target: 'https://api.seniverse.com/',
changeOrigin: true,
pathRewrite: { '^/api/v1/weather': '/v3/weather' },
},
},
alias: { alias: {
api: resolve(__dirname, './src/services/'), api: resolve(__dirname, './src/services/'),
components: resolve(__dirname, './src/components'), components: resolve(__dirname, './src/components'),
config: resolve(__dirname, './src/utils/config'), config: resolve(__dirname, './src/utils/config'),
models: resolve(__dirname, './src/models'),
routes: resolve(__dirname, './src/routes'),
services: resolve(__dirname, './src/services'),
themes: resolve(__dirname, './src/themes'), themes: resolve(__dirname, './src/themes'),
utils: resolve(__dirname, './src/utils'), utils: resolve(__dirname, './src/utils'),
}, },
antd: {},
// a lower cost way to genereate sourcemap, default is cheap-module-source-map, could save 60% time in dev hotload
devtool: 'eval',
dva: { immer: true },
dynamicImport: {
loading: 'components/Loader/Loader',
},
extraBabelPresets: ['@lingui/babel-preset-react'], extraBabelPresets: ['@lingui/babel-preset-react'],
extraBabelPlugins: [ extraBabelPlugins: [
[ [
@@ -93,5 +34,99 @@ export default {
}, },
'lodash', 'lodash',
], ],
[
'import',
{
libraryName: '@ant-design/icons',
libraryDirectory: 'es/icons',
camel2DashComponentName: false,
},
'ant-design-icons',
],
], ],
hash: true,
ignoreMomentLocale: true,
// umi3 comple node_modules by default, could be disable
nodeModulesTransform: {
type: 'none',
exclude: [],
},
// Webpack Configuration
proxy: {
'/api/v1/weather': {
target: 'https://api.seniverse.com/',
changeOrigin: true,
pathRewrite: { '^/api/v1/weather': '/v3/weather' },
},
},
targets: { ie: 9 },
// Theme for antd
// https://ant.design/docs/react/customize-theme
theme: lessToJs(
fs.readFileSync(path.join(__dirname, './src/themes/default.less'), 'utf8')
),
chainWebpack: function (config, { webpack }) {
config.merge({
optimization: {
minimize: true,
splitChunks: {
chunks: 'all',
minSize: 30000,
minChunks: 3,
automaticNameDelimiter: '.',
cacheGroups: {
react: {
name: 'react',
priority: 20,
test: /[\\/]node_modules[\\/](react|react-dom|react-dom-router)[\\/]/,
},
antd: {
name: 'antd',
priority: 20,
test: /[\\/]node_modules[\\/](antd|@ant-design\/icons)[\\/]/,
},
'echarts-gl': {
name: 'echarts-gl',
priority: 30,
test: /[\\/]node_modules[\\/]echarts-gl[\\/]/,
},
zrender: {
name: 'zrender',
priority: 30,
test: /[\\/]node_modules[\\/]zrender[\\/]/,
},
echarts: {
name: 'echarts',
priority: 20,
test: /[\\/]node_modules[\\/](echarts|echarts-for-react|echarts-liquidfill)[\\/]/,
},
highcharts: {
name: 'highcharts',
priority: 20,
test: /[\\/]node_modules[\\/]highcharts[\\/]/,
},
recharts: {
name: 'recharts',
priority: 20,
test: /[\\/]node_modules[\\/]recharts[\\/]/,
},
draftjs: {
name: 'draftjs',
priority: 30,
test: /[\\/]node_modules[\\/](draft-js|react-draft-wysiwyg|draftjs-to-html|draftjs-to-markdown)[\\/]/,
},
async: {
chunks: 'async',
minChunks: 2,
name: 'async',
maxInitialRequests: 1,
minSize: 0,
priority: 5,
reuseExistingChunk: true,
},
},
},
},
})
},
} }
+2 -2
View File
@@ -71,11 +71,11 @@ npm run start
## 支持环境 ## 支持环境
现代浏览器及 IE9。 现代浏览器。
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------- | --------- | --------- | --------- | --------- | | --------- | --------- | --------- | --------- | --------- |
| IE9, IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions |IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
## 参与贡献 ## 参与贡献
+4 -4
View File
@@ -9,9 +9,9 @@
<div align="center"> <div align="center">
A excellent front-end solution for enterprise applications. An excellent front-end solution for enterprise applications.
[![antd](https://img.shields.io/badge/antd-^3.10.0-blue.svg?style=flat-square)](https://github.com/ant-design/ant-design) [![antd](https://img.shields.io/badge/antd-^4.0.0-blue.svg?style=flat-square)](https://github.com/ant-design/ant-design)
[![umi](https://img.shields.io/badge/umi-^2.2.1-orange.svg?style=flat-square)](https://github.com/umijs/umi) [![umi](https://img.shields.io/badge/umi-^2.2.1-orange.svg?style=flat-square)](https://github.com/umijs/umi)
[![GitHub issues](https://img.shields.io/github/issues/zuiidea/antd-admin.svg?style=flat-square)](https://github.com/zuiidea/antd-admin/issues) [![GitHub issues](https://img.shields.io/github/issues/zuiidea/antd-admin.svg?style=flat-square)](https://github.com/zuiidea/antd-admin/issues)
[![MIT](https://img.shields.io/dub/l/vibe-d.svg?style=flat-square)](http://opensource.org/licenses/MIT) [![MIT](https://img.shields.io/dub/l/vibe-d.svg?style=flat-square)](http://opensource.org/licenses/MIT)
@@ -71,11 +71,11 @@ npm run start
## Browsers support ## Browsers support
Modern browsers and IE9. Modern browsers.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------- | --------- | --------- | --------- | --------- | | --------- | --------- | --------- | --------- | --------- |
| IE9, IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions |IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
## Contributing ## Contributing
BIN
View File
Binary file not shown.
-8
View File
@@ -1,8 +0,0 @@
const fs = require('fs')
const path = require('path')
const lessToJs = require('less-vars-to-js')
module.exports = () => {
const themePath = path.join(__dirname, '../src/themes/default.less')
return lessToJs(fs.readFileSync(themePath, 'utf8'))
}
+86
View File
@@ -0,0 +1,86 @@
# API configuration
## Why
In the use of `redux` or `dva` projects, we often write functions like the following `service` layer to make the code structure clearer, but it is easy to see that we will write a lot of similar code in `antd -admin@5.0`, using the more concise configuration method to achieve the same function.
```javascript
Export async function login(data) {
  Return request({
    Url: '/api/v1/user/login',
    Method: 'post',
    Data,
  })
}
```
## Configuration and use
In the `src/services/api.js` file, you will see the following configuration object, the object's key is used to call the function name, the object's value is the requested `url`, the default request method is `GET`, The format of the value of the other request mode object is `'method url'`.
```javascript
Export default {
  ...
  queryUser: '/user/:id',
  queryUserList: '/users',
  updateUser: 'Patch /user/:id',
  createUser: 'POST /user/:id',
  removeUser: 'DELETE /user/:id',
  removeUserList: 'POST /users/delete',
  ...
}
```
Used in other files
```javascript
Import { queryUser } from 'api'
// in the general file
...
queryUser(option).then(data => console.log(data))
...
/ / Model file
...
Yield call(queryUser, option)
...
```
## Method to realize
Refer to the `src/services/index.js` file to traverse the api configuration. Each property returns the corresponding encapsulated request function.
```javascript
Import request from 'utils/request'
Import { apiPrefix } from 'utils/config'
Import api from './api'
Const gen = params => {
  Let url = apiPrefix + params
  Let method = 'GET'
  Const paramsArray = params.split(' ')
  If (paramsArray.length === 2) {
    Method = paramsArray[0]
    Url = apiPrefix + paramsArray[1]
  }
  Return function(data) {
    Return request({
      Url,
      Data,
      Method,
    })
  }
}
Const APIFunction = {}
For (const key in api) {
  APIFunction[key] = gen(api[key])
}
Module.exports = APIFunction
```
+3 -3
View File
@@ -8,7 +8,7 @@
<div align="center"> <div align="center">
A excellent front-end solution for enterprise applications. An excellent front-end solution for enterprise applications.
[![antd](https://img.shields.io/badge/antd-^3.10.0-blue.svg?style=flat-square)](https://github.com/ant-design/ant-design) [![antd](https://img.shields.io/badge/antd-^3.10.0-blue.svg?style=flat-square)](https://github.com/ant-design/ant-design)
[![umi](https://img.shields.io/badge/umi-^2.2.1-orange.svg?style=flat-square)](https://github.com/umijs/umi) [![umi](https://img.shields.io/badge/umi-^2.2.1-orange.svg?style=flat-square)](https://github.com/umijs/umi)
@@ -68,11 +68,11 @@ npm run start
## Browsers support ## Browsers support
Modern browsers and IE9. Modern browsers.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------- | --------- | --------- | --------- | --------- | | --------- | --------- | --------- | --------- | --------- |
| IE9, IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions |IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
## Contributing ## Contributing
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 194 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 118 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 199 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 124 KiB

+5
View File
@@ -2,6 +2,11 @@
- [Quick Start](getting-started.md) - [Quick Start](getting-started.md)
- Customization - Customization
- [Configuration](configuration.md) - [Configuration](configuration.md)
- [API Configuration](API-configuration.md)
- [I18n](i18n.md)
- [Layout](layout.md)
- [Request](request.md)
- Guide - Guide
- [Deploy](deploy.md) - [Deploy](deploy.md)
- [Change Log](change-log.md) - [Change Log](change-log.md)
- [FAQ](faq.md)
+1 -1
View File
@@ -28,7 +28,7 @@
- Support internationalization, extract source fields from source code, load language packs on demand, and automatically translate online. - Support internationalization, extract source fields from source code, load language packs on demand, and automatically translate online.
- Support for the introduction of `ant-design-pro` components, `lodash` functions on demand. - Support for the introduction `lodash` functions on demand.
     
- Support multiple layouts, which rules can be used according to the rules. - Support multiple layouts, which rules can be used according to the rules.
+10 -10
View File
@@ -12,7 +12,7 @@ You can do some custom configuration in `/src/utils/config.js`:
- Type: `String` - Type: `String`
Configure the copyright notice to apply to the login page, at the bottom of the `Primay` layout. Configure the copyright notice to apply to the login page, at the bottom of the `Primary` layout.
## logoPath ## logoPath
@@ -24,7 +24,7 @@ You can do some custom configuration in `/src/utils/config.js`:
- Type: `String` - Type: `String`
Configure the prefix of the interface in the project. The interface related documents can be viewed [API configuration]() Configure the prefix of the interface in the project. The interface related documents can be viewed [API configuration](API-configuration.md)
## fixedHeader ## fixedHeader
@@ -39,25 +39,25 @@ You can do some custom configuration in `/src/utils/config.js`:
Configuration? Which routes use which layout, unspecified route uses the default layout `Public`, the project currently has `Primary` and `Public` layouts, Configuration? Which routes use which layout, unspecified route uses the default layout `Public`, the project currently has `Primary` and `Public` layouts,
     The default configuration is as follows:      The default configuration is as follows:
```js ```javascript
layouts: [ layouts: [
{ {
name: 'primary', name: 'primary',
include: [/.*/], include: [/.*/],
exlude: [/(\/(en|zh))*\/login/], exclude: [/(\/(en|zh))*\/login/],
}, },
], ],
``` ```
The object properties for each layout are as follows: The object properties for each layout are as follows:
- `name` - The name of the layout; - `name` - The name of the layout;
- `include` - Specifies a list of routing rules that use this layout, which can be a regular expression or a string; - `include` - Specifies a list of routing rules that use this layout, which can be a regular expression or a string;
- `exlude` - Specifies a list of routing rules that do not use this layout, which can be a regular expression or a string. - `exclude` - Specifies a list of routing rules that do not use this layout, which can be a regular expression or a string.
> Note: `exlude` takes precedence over `include`, and the layout behind it has a higher priority than the previous layout. The development process may need to be combined with the layout in the `src/layouts` directory. For details, see [Using Layout](). > Note: `exclude` takes precedence over `include`, and the layout previous it has a higher priority than the behind layout. The development process may need to be combined with the layout in the `src/layouts` directory. For details, see [Using Layout](./layout.md).
## i18n ## i18n
@@ -65,7 +65,7 @@ You can do some custom configuration in `/src/utils/config.js`:
Configure internationalization, the default configuration is as follows: Configure internationalization, the default configuration is as follows:
```js ```javascript
i18n: { i18n: {
languages: [ languages: [
{ {
@@ -91,9 +91,9 @@ You can do some custom configuration in `/src/utils/config.js`:
- `key` - The `key` of the language is applied to the page url to distinguish the language, and also corresponds to the language package folder name in the `src/locales` directory; - `key` - The `key` of the language is applied to the page url to distinguish the language, and also corresponds to the language package folder name in the `src/locales` directory;
- `title` - The name of the language, at the bottom of the login page, at the top of the `Primay` layout, the language switch is displayed; - `title` - The name of the language, at the bottom of the login page, at the top of the `Primary` layout, the language switch is displayed;
- `flag` - The path of the flag icon of the language, the language switching display at the top of the `Primay` layout. - `flag` - The path of the flag icon of the language, the language switching display at the top of the `Primary` layout.
### i18n.defaultLanguage ### i18n.defaultLanguage
+2
View File
@@ -2,6 +2,8 @@
After the development is completed and verified in the development environment, it needs to be deployed to our users. After the development is completed and verified in the development environment, it needs to be deployed to our users.
![i18n](./_media/term_build.svg)
## Build ## Build
First execute the following command, First execute the following command,
+9
View File
@@ -0,0 +1,9 @@
# FAQ
Most asked
## create new page
1. just copy a page in /src/pages (route here auto generated by [umi](https://umijs.org/guide/router.html#basic-routing))
2. modify namespace/pathToRegexp in model.js
3. modify mock route.js to add a route
+75
View File
@@ -0,0 +1,75 @@
# globalization
## Add language
Take Japanese as an example.
![i18n](../_media/term_i18n.svg)
1. Add a language pack local file, `ja` is the Japanese language code, and a list of languages ​​that support translation [有道智云](http://ai.youdao.com/docs/doc-trans-api.s#p05), the `src/locales/ja/messages.json` file will be generated after running the following command.
```bash 
npm run add-locale ja
```
2. Extract the fields in the code that need to be translated, ie `<Trans>?message</Trans>`, `` i18n.t`message `` in the `message` field, run the following command after `src/locales/ja /messages.json` will appear after the extracted field configuration.
```bash 
npm run extract
```
You will see the following information:
```bash
Catalog statistics:
┌─────────────┬─────────────┬─────────┐
│ Language │ Total count │ Missing │
├─────────────┼─────────────┼─────────┤
│ en (source) │ 52 │ - │
│ ja │ 52 │ 52 │
│ zh │ 52 │ 0 │
└─────────────┴─────────────┴─────────┘
```
3. At the same time, we have added the relevant configuration in `src/utils/config.js`.
```javascript
{
...
i18n: {
languages: [
...
{
key:'ja',
title: '日本語',
flag: '/japanese.svg',
},
],
},
}
```
> Routing related effects, after the configuration `npm run start` takes effect after restart.
4. Use the built-in commands for automatic translation. You will see the translated configuration in `src/locales/ja/messages.json`.
```bash
npm run trans:only
```
You will see the following information:
```bash
start: en -> ja
...
youdao: en -> ja: Unpublished -> 未発表
youdao: en -> ja: Update -> 更新
youdao: en -> ja: Update User -> ユーザーの更新
youdao: en -> ja: Username -> 名
...
All translations have been completed.
```
> `npm run trans` will execute `npm run extract` and `npm run trans:only` in order.
5. Finally, you can adjust the inaccurate fields in `src/locales/ja/messages.json`. Start the development mode `npm run start`, open [http://localhost:7000/ja/login](http://localhost:7000/ja/login) and you will see the Japanese version of the app.
+60
View File
@@ -0,0 +1,60 @@
# Layout
## Add a new layout
Take a new layout named `secondary` as an example to make the route starting with `seconday` use this layout.
1. Add related configuration in `src/utils/config.js`. For details, please refer to [layouts](/configuration?id=layouts).
```javascript
   layouts: [
           {
               name: 'primary',
               include: [/.*/],
               exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/seconday\/(.*)/],
           },
           {
               name: 'secondary',
               include: [/(\/(en|zh))*\/seconday\/(.*)/],
           },
   ],
```
2. Add the `secondary` layout component to the `src/layouts/BaseLayout.js` file.
```javascript
   import SecondaryLayout from './SecondaryLayout'
   const LayoutMap = {
     Primary: PrimaryLayout,
     Public: PublicLayout,
     Secondary: SecondaryLayout,
   }
```
3. Add the `SecondaryLayout.js` file to the `src/layouts/` directory.
```javascript
   import React from 'react'
   export default ({ children }) => {
     Return (
       <div>
         <h1>Seconday</h1>
         {children}
       </div>
     )
   }
```
4. Add a `seconday/index.js` file to the `src/pages/` directory.
```javascript
   import React from 'react'
   export default ({ children }) => {
     Return <div>Seconday page Content</div>
   }
```
5. Finally, start the development mode `npm run start`, open [http://localhost:7000/seconday/](http://localhost:7000/seconday/) and you will see the page for the `seconday` layout.
-6
View File
@@ -1,6 +0,0 @@
{
"name": "antd-admin-devdoc",
"alias": ["devdoc-antd-admin.now.sh", "devdoc.antd-admin.zuiidea.com"],
"type": "static",
"public": true
}
-6
View File
@@ -1,6 +0,0 @@
{
"name": "antd-admin-doc",
"alias": ["doc-antd-admin.now.sh", "doc.antd-admin.zuiidea.com"],
"type": "static",
"public": true
}
+22
View File
@@ -0,0 +1,22 @@
# HTTP request
this project use axios for http service, file located in src/utils/request.js
## Custom Header
As for privilege access or modify cookie, you could add header param by yourself
```
axios.defaults.headers.common['Authorization'] = 'token'
```
Or
```
axios.interceptors.request.use(function (config) {
config.headers.token = window.localStorage.getItem('token');
return config;
}, function (error) {
return Promise.reject(error);
});
```
+86
View File
@@ -0,0 +1,86 @@
# 接口配置
## 为什么
在使用了`redux`或者`dva`项目中,我们经常会写类似下面的`service`层的函数,使代码结构更清晰,但是很容易看出,我们会写很多相似的代码,在`antd-admin@5.0`中,使用了更加简洁的配置方式实现了相同的功能。
```javascript
export async function login(data) {
return request({
url: '/api/v1/user/login',
method: 'post',
data,
})
}
```
## 配置和使用
在`src/services/api.js`文件中,你会看到如下配置对象,对象的键用于调用时的函数名称,对象的值为请求的`url`,默认请求方式为`GET`,如果是其他请求方式对象的值的格式则为`'method url'`。
```javascript
export default {
...
queryUser: '/user/:id',
queryUserList: '/users',
updateUser: 'Patch /user/:id',
createUser: 'POST /user/:id',
removeUser: 'DELETE /user/:id',
removeUserList: 'POST /users/delete',
...
}
```
在其他文件中使用
```javascript
import { queryUser } from 'api'
// 一般文件中
...
queryUser(option).then(data => console.log(data))
...
// model文件中
...
yield call(queryUser, option)
...
```
## 实现方式
参考`src/services/index.js`文件,对api配置进行遍历,每个属性都返回对应的封装后的request函数。
```javascript
import request from 'utils/request'
import { apiPrefix } from 'utils/config'
import api from './api'
const gen = params => {
let url = apiPrefix + params
let method = 'GET'
const paramsArray = params.split(' ')
if (paramsArray.length === 2) {
method = paramsArray[0]
url = apiPrefix + paramsArray[1]
}
return function(data) {
return request({
url,
data,
method,
})
}
}
const APIFunction = {}
for (const key in api) {
APIFunction[key] = gen(api[key])
}
module.exports = APIFunction
```
+2 -2
View File
@@ -68,11 +68,11 @@ npm run start
## 支持环境 ## 支持环境
现代浏览器及 IE9。 现代浏览器。
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------- | --------- | --------- | --------- | --------- | | --------- | --------- | --------- | --------- | --------- |
| IE9, IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions |IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
## 参与贡献 ## 参与贡献
+4
View File
@@ -2,6 +2,10 @@
- [快速上手](zh-cn/getting-started.md) - [快速上手](zh-cn/getting-started.md)
- 定制化 - 定制化
- [配置项](zh-cn/configuration.md) - [配置项](zh-cn/configuration.md)
- [接口配置](zh-cn/API-configuration.md)
- [国际化](zh-cn/i18n.md)
- [布局](zh-cn/layout.md)
- [http 请求](zh-cn/request.md)
- 指南 - 指南
- [部署](zh-cn/deploy.md) - [部署](zh-cn/deploy.md)
- [更新日志](zh-cn/change-log.md) - [更新日志](zh-cn/change-log.md)
+1 -1
View File
@@ -28,7 +28,7 @@
- 支持国际化,源码中抽离翻译字段,按需加载语言包,自动在线翻译。 - 支持国际化,源码中抽离翻译字段,按需加载语言包,自动在线翻译。
- 支持按需引入 `ant-design-pro` 组件、`lodash` 函数。 - 支持按需引入 `lodash` 函数。
- 支持多布局,可根据规则规定哪些路由使用哪种布局。 - 支持多布局,可根据规则规定哪些路由使用哪种布局。
+6 -6
View File
@@ -24,7 +24,7 @@
- 类型: `String` - 类型: `String`
配置项目中接口的前缀,接口相关文档可查看 [接口配置化]() 配置项目中接口的前缀,接口相关文档可查看 [接口配置](API-configuration.md)
## fixedHeader ## fixedHeader
@@ -39,12 +39,12 @@
配置哪些路由使用哪种布局,未指定路由使用默认布局 `Public`,项目中目前有 `Primary` 和 `Public` 两种布局, 配置哪些路由使用哪种布局,未指定路由使用默认布局 `Public`,项目中目前有 `Primary` 和 `Public` 两种布局,
默认配置如下: 默认配置如下:
```js ```javascript
layouts: [ layouts: [
{ {
name: 'primary', name: 'primary',
include: [/.*/], include: [/.*/],
exlude: [/(\/(en|zh))*\/login/], exclude: [/(\/(en|zh))*\/login/],
}, },
], ],
``` ```
@@ -55,9 +55,9 @@
- `include` - 指定使用该布局的路由规则列表,规则可为正则表达式或者字符串; - `include` - 指定使用该布局的路由规则列表,规则可为正则表达式或者字符串;
- `exlude` - 指定不使用该布局的路由规则列表,规则可为正则表达式或者字符串。 - `exclude` - 指定不使用该布局的路由规则列表,规则可为正则表达式或者字符串。
> 注意:`exlude` 优先级高于 `include`,后面的布局优先级高于前面的布局。开发过程中可能需要结合`src/layouts`目录下的布局使用,具体方法可查看 [使用布局]()。 > 注意:`exclude` 优先级高于 `include`,前面的布局优先级高于后面的布局。开发过程中可能需要结合`src/layouts`目录下的布局使用,具体方法可查看 [使用布局](./layout.md)。
## i18n ## i18n
@@ -65,7 +65,7 @@
配置国际化,默认配置如下: 配置国际化,默认配置如下:
```js ```javascript
i18n: { i18n: {
languages: [ languages: [
{ {
+3 -1
View File
@@ -2,6 +2,8 @@
完成开发并且在开发环境验证之后,就需要部署给我们的用户了。 完成开发并且在开发环境验证之后,就需要部署给我们的用户了。
![i18n](../_media/term_build.svg)
## 构建 ## 构建
先执行下面的命令, 先执行下面的命令,
@@ -34,7 +36,7 @@ File sizes after gzip:
`build` 命令会打包所有的资源,包含 JavaScript, CSS, web fonts, images, html 等。你可以在 `dist/` 目录下找到这些文件。 `build` 命令会打包所有的资源,包含 JavaScript, CSS, web fonts, images, html 等。你可以在 `dist/` 目录下找到这些文件。
> 如果  有使用 HashHistory 、 部署 html 到非根目录、静态化等需求,请查看[Umi 部署](https://umijs.org/zh/guide/deploy.html)。 > 如果有使用 HashHistory 、 部署 html 到非根目录、静态化等需求,请查看[Umi 部署](https://umijs.org/zh/guide/deploy.html)。
## 本地验证 ## 本地验证
+7
View File
@@ -0,0 +1,7 @@
# 问题集锦
## 新建页面
1. 直接从/src/pages复制一个page (会自动创建路由[umi](https://umijs.org/zh/guide/router.html#%E7%BA%A6%E5%AE%9A%E5%BC%8F%E8%B7%AF%E7%94%B1))
2. 修改 namespace/pathToRegexp 在 model.js
3. 修改 mock中route.js增加一条route
+75
View File
@@ -0,0 +1,75 @@
# 国际化
## 新增应用语言
以新增日语为例。
![i18n](../_media/term_i18n.svg)
1. 添加语言包本地文件,`ja` 为日语的语言代码,支持翻译的语言列表参考 [有道智云](http://ai.youdao.com/docs/doc-trans-api.s#p05),运行下面命令后会生成 `src/locales/ja/messages.json` 文件。
```bash
npm run add-locale ja
```
2. 提取代码中需要翻译的字段,即 `<Trans>message</Trans>`、`` intl.formatMessage({ id: 'message `` 中 `message` 字段,运行下面命令后 `src/locales/ja/messages.json' }) 将会出现提取后的字段配置。
```bash
npm run extract
```
你将看到如下信息:
```bash
Catalog statistics:
┌─────────────┬─────────────┬─────────┐
│ Language │ Total count │ Missing │
├─────────────┼─────────────┼─────────┤
│ en (source) │ 52 │ - │
│ ja │ 52 │ 52 │
│ zh │ 52 │ 0 │
└─────────────┴─────────────┴─────────┘
```
3. 与此同时,我们在 `src/utils/config.js` 新增相关配置。
```javascript
{
...
i18n: {
languages: [
...
{
key:'ja',
title: '日本語',
flag: '/japanese.svg',
},
],
},
}
```
>  路由相关效果,配置后 `npm run start` 重启后生效。
4. 使用内置的命令进行自动翻译,在 `src/locales/ja/messages.json` 中将会看到翻译后的配置。
```bash
npm run trans:only
```
你将看到如下信息:
```bash
start: en -> ja
...
youdao: en -> ja: Unpublished -> 未発表
youdao: en -> ja: Update -> 更新
youdao: en -> ja: Update User -> ユーザーの更新
youdao: en -> ja: Username -> 名
...
All translations have been completed.
```
> `npm run trans` 将会依次执行 `npm run extract` 和 `npm run trans:only`
5. 最后,可以在 `src/locales/ja/messages.json` 中对翻译不准确的的字段进行调整。启动开发模式 `npm run start`,打开 [http://localhost:7000/ja/login](http://localhost:7000/ja/login),你将看到日语版本的应用。
+60
View File
@@ -0,0 +1,60 @@
# 布局
## 新增布局
以新增名为 `secondary` 的布局为例,使以 `seconday` 开头的路由都使用该布局。
1. 在 `src/utils/config.js` 新增相关配置,参数详细请查看 [layouts](/zh-cn/configuration?id=layouts)。
```javascript
layouts: [
{
name: 'primary',
include: [/.*/],
exclude: [/(\/(en|zh))*\/login/, /(\/(en|zh))*\/seconday\/(.*)/],
},
{
name: 'secondary',
include: [/(\/(en|zh))*\/seconday\/(.*)/],
},
],
```
2. 在`src/layouts/BaseLayout.js` 文件中新增 `secondary` 布局组件。
```javascript
import SecondaryLayout from './SecondaryLayout'
const LayoutMap = {
primary: PrimaryLayout,
public: PublicLayout,
secondary: SecondaryLayout,
}
```
3. 在`src/layouts/` 目录中新增 `SecondaryLayout.js` 文件。
```javascript
import React from 'react'
export default ({ children }) => {
return (
<div>
<h1>Seconday</h1>
{children}
</div>
)
}
```
4. 在`src/pages/` 目录中新增 `seconday/index.js` 文件。
```javascript
import React from 'react'
export default ({ children }) => {
return <div>Seconday page Content</div>
}
```
5. 最后,启动开发模式 `npm run start`,打开 [http://localhost:7000/seconday/](http://localhost:7000/seconday/),你将看到 `seconday` 布局的页面。
+24
View File
@@ -0,0 +1,24 @@
# HTTP请求
本项目使用了axios提供http请求服务,文件在src/utils/request.js
## 自定义Header
为了提供鉴权、修改cookie等服务,可以手动修改Header
```
axios.defaults.headers.common['Authorization'] = 'token'
```
或者
```
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
config.headers.token = window.localStorage.getItem('token');
return config;
}, function (error) {
return Promise.reject(error);
});
```
+36
View File
@@ -0,0 +1,36 @@
{
"name": "Antd-Admin",
"start_url": ".",
"display": "standalone",
"background_color": "#fff",
"description": "A front-end solution for enterprise applications built upon Ant Design and UmiJS",
"icons": [{
"src": "logo/logo@96.png",
"sizes": "72x72"
},
{
"src": "logo/logo@128.png",
"sizes": "128x128"
},
{
"src": "logo/logo@144.png",
"sizes": "144x144"
},
{
"src": "logo/logo@152.png",
"sizes": "152x152"
},
{
"src": "logo/logo@192.png",
"sizes": "192x192"
},
{
"src": "logo/logo@384.png",
"sizes": "384x384"
},
{
"src": "logo/logo@512.png",
"sizes": "512x512"
}
]
}
+14 -17
View File
@@ -17,25 +17,22 @@ export function randomNumber(min, max) {
} }
export function randomAvatar() { export function randomAvatar() {
// https://uifaces.co
const avatarList = [ const avatarList = [
'https://randomuser.me/api/portraits/men/32.jpg', 'photo-1549492864-2ec7d66ffb04.jpeg',
'https://images.pexels.com/photos/415829/pexels-photo-415829.jpeg?h=350&auto=compress&cs=tinysrgb', 'photo-1480535339474-e083439a320d.jpeg',
'https://d3iw72m71ie81c.cloudfront.net/female-17.jpg', 'photo-1523419409543-a5e549c1faa8.jpeg',
'https://randomuser.me/api/portraits/men/35.jpg', 'photo-1519648023493-d82b5f8d7b8a.jpeg',
'https://pbs.twimg.com/profile_images/835224725815246848/jdMBCxHS.jpg', 'photo-1523307730650-594bc63f9d67.jpeg',
'https://pbs.twimg.com/profile_images/584098247641300992/N25WgvW_.png', 'photo-1522962506050-a2f0267e4895.jpeg',
'https://d3iw72m71ie81c.cloudfront.net/male-5.jpg', 'photo-1489779162738-f81aed9b0a25.jpeg',
'https://images.pexels.com/photos/413723/pexels-photo-413723.jpeg?h=350&auto=compress&cs=tinysrgb', 'photo-1534308143481-c55f00be8bd7.jpeg',
'https://randomuser.me/api/portraits/women/44.jpg', 'photo-1519336555923-59661f41bb45.jpeg',
'https://randomuser.me/api/portraits/women/68.jpg', 'photo-1551438632-e8c7d9a5d1b7.jpeg',
'https://randomuser.me/api/portraits/women/65.jpg', 'photo-1525879000488-bff3b1c387cf.jpeg',
'https://randomuser.me/api/portraits/men/43.jpg', 'photo-1487412720507-e7ab37603c6f.jpeg',
'https://tinyfac.es/data/avatars/475605E3-69C5-4D2B-8727-61B7BB8C4699-500w.jpeg', 'photo-1510227272981-87123e259b17.jpeg'
'https://pbs.twimg.com/profile_images/943227488292962306/teiNNAiy.jpg',
'https://randomuser.me/api/portraits/men/46.jpg'
] ]
return avatarList[randomNumber(0, avatarList.length - 1)] return `//image.zuiidea.com/${avatarList[randomNumber(0, avatarList.length - 1)]}?imageView2/1/w/200/h/200/format/webp/q/75|imageslim`
} }
export const Constant = { export const Constant = {
+1 -1
View File
@@ -105,7 +105,7 @@ const Dashboard = Mock.mock({
content: content:
"I'm selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can't handle me at my worst, then you sure as hell don't deserve me at my best.", "I'm selfish, impatient and a little insecure. I make mistakes, I am out of control and at times hard to handle. But if you can't handle me at my worst, then you sure as hell don't deserve me at my best.",
avatar: avatar:
'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236', '//cdn.antd-admin.zuiidea.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
}, },
numbers: [ numbers: [
{ {
+67 -12
View File
@@ -6,14 +6,24 @@ const database = [
id: '1', id: '1',
icon: 'dashboard', icon: 'dashboard',
name: 'Dashboard', name: 'Dashboard',
zhName: '仪表盘', zh: {
name: '仪表盘'
},
'pt-br': {
name: 'Dashboard'
},
route: '/dashboard', route: '/dashboard',
}, },
{ {
id: '2', id: '2',
breadcrumbParentId: '1', breadcrumbParentId: '1',
name: 'Users', name: 'Users',
zhName: '用户管理', zh: {
name: '用户管理'
},
'pt-br': {
name: 'Usuário'
},
icon: 'user', icon: 'user',
route: '/user', route: '/user',
}, },
@@ -21,7 +31,12 @@ const database = [
id: '7', id: '7',
breadcrumbParentId: '1', breadcrumbParentId: '1',
name: 'Posts', name: 'Posts',
zhName: '文章管理', zh: {
name: '用户管理'
},
'pt-br': {
name: 'Posts'
},
icon: 'shopping-cart', icon: 'shopping-cart',
route: '/post', route: '/post',
}, },
@@ -30,14 +45,24 @@ const database = [
menuParentId: '-1', menuParentId: '-1',
breadcrumbParentId: '2', breadcrumbParentId: '2',
name: 'User Detail', name: 'User Detail',
zhName: '用户详情', zh: {
name: '用户详情'
},
'pt-br': {
name: 'Detalhes do usuário'
},
route: '/user/:id', route: '/user/:id',
}, },
{ {
id: '3', id: '3',
breadcrumbParentId: '1', breadcrumbParentId: '1',
name: 'Request', name: 'Request',
zhName: 'Request', zh: {
name: 'Request'
},
'pt-br': {
name: 'Requisição'
},
icon: 'api', icon: 'api',
route: '/request', route: '/request',
}, },
@@ -45,7 +70,12 @@ const database = [
id: '4', id: '4',
breadcrumbParentId: '1', breadcrumbParentId: '1',
name: 'UI Element', name: 'UI Element',
zhName: 'UI组件', zh: {
name: 'UI组件'
},
'pt-br': {
name: 'Elementos UI'
},
icon: 'camera-o', icon: 'camera-o',
}, },
{ {
@@ -53,15 +83,25 @@ const database = [
breadcrumbParentId: '4', breadcrumbParentId: '4',
menuParentId: '4', menuParentId: '4',
name: 'Editor', name: 'Editor',
zhName: 'Editor', zh: {
name: 'Editor'
},
'pt-br': {
name: 'Editor'
},
icon: 'edit', icon: 'edit',
route: '/UIElement/editor', route: '/editor',
}, },
{ {
id: '5', id: '5',
breadcrumbParentId: '1', breadcrumbParentId: '1',
name: 'Charts', name: 'Charts',
zhName: 'Charts', zh: {
name: 'Charts'
},
'pt-br': {
name: 'Graficos'
},
icon: 'code-o', icon: 'code-o',
}, },
{ {
@@ -69,7 +109,12 @@ const database = [
breadcrumbParentId: '5', breadcrumbParentId: '5',
menuParentId: '5', menuParentId: '5',
name: 'ECharts', name: 'ECharts',
zhName: 'ECharts', zh: {
name: 'ECharts'
},
'pt-br': {
name: 'ECharts'
},
icon: 'line-chart', icon: 'line-chart',
route: '/chart/ECharts', route: '/chart/ECharts',
}, },
@@ -78,7 +123,12 @@ const database = [
breadcrumbParentId: '5', breadcrumbParentId: '5',
menuParentId: '5', menuParentId: '5',
name: 'HighCharts', name: 'HighCharts',
zhName: 'HighCharts', zh: {
name: 'HighCharts'
},
'pt-br': {
name: 'HighCharts'
},
icon: 'bar-chart', icon: 'bar-chart',
route: '/chart/highCharts', route: '/chart/highCharts',
}, },
@@ -87,7 +137,12 @@ const database = [
breadcrumbParentId: '5', breadcrumbParentId: '5',
menuParentId: '5', menuParentId: '5',
name: 'Rechartst', name: 'Rechartst',
zhName: 'Rechartst', zh: {
name: 'Rechartst'
},
'pt-br': {
name: 'Rechartst'
},
icon: 'area-chart', icon: 'area-chart',
route: '/chart/Recharts', route: '/chart/Recharts',
}, },
+2 -1
View File
@@ -1,4 +1,5 @@
import { Mock, Constant, qs, randomAvatar } from './_utils' import { Mock, Constant, randomAvatar } from './_utils'
import qs from 'qs'
const { ApiPrefix } = Constant const { ApiPrefix } = Constant
+50 -50
View File
@@ -1,21 +1,21 @@
{ {
"name": "antd-admin", "name": "antd-admin",
"version": "5.0.0-beta", "version": "5.3.0",
"private": true, "license": "MIT",
"description": "An admin dashboard application demo built upon Ant Design and UmiJS", "description": "An admin dashboard application demo built upon Ant Design and UmiJS",
"dependencies": { "dependencies": {
"@lingui/react": "^2.7.0", "@ant-design/icons": "^4.2.2",
"ant-design-pro": "^2.1.0", "@lingui/react": "^2.8.0",
"antd": "^3.10.0", "antd": "^4.0.0",
"axios": "^0.18.0", "axios": "^0.19.0",
"babel-core": "7.0.0-bridge.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"d3-shape": "^1.2.2", "d3-shape": "^1.3.0",
"draftjs-to-html": "^0.8.4", "draft-js": "^0.11.7",
"draftjs-to-markdown": "^0.5.1", "draftjs-to-html": "^0.9.0",
"draftjs-to-markdown": "^0.6.0",
"dva-model-extend": "^0.1.2", "dva-model-extend": "^0.1.2",
"echarts": "^4.2.0-rc.2", "echarts": "^4.4.0",
"echarts-for-react": "^2.0.15-beta.0", "echarts-for-react": "^2.0.15-beta.1",
"echarts-gl": "^1.1.1", "echarts-gl": "^1.1.1",
"echarts-liquidfill": "^2.0.2", "echarts-liquidfill": "^2.0.2",
"enquire-js": "^0.2.1", "enquire-js": "^0.2.1",
@@ -25,49 +25,48 @@
"lodash": "^4.17.11", "lodash": "^4.17.11",
"md5": "^2.2.1", "md5": "^2.2.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^2.4.0", "path-to-regexp": "^6.1.0",
"prop-types": "^15.6.2", "prop-types": "^15.7.0",
"qs": "^6.5.2", "qs": "^6.9.0",
"rc-tween-one": "^2.2.8", "react-adsense": "^0.1.0",
"react-adsense": "^0.0.6", "react-countup": "^4.2.0",
"react-countup": "^4.0.0-alpha.6", "react-draft-wysiwyg": "^1.13.0",
"react-draft-wysiwyg": "^1.12.13", "react-helmet": "^6.0.0",
"react-helmet": "^5.2.0", "react-highcharts": "^16.1.0",
"react-highcharts": "^16.0.2", "react-perfect-scrollbar": "^1.5.0",
"react-perfect-scrollbar": "^1.4.0", "recharts": "^2.0.0-beta.3",
"recharts": "^1.3.0", "store": "^2.0.0"
"store": "^2.0.12"
}, },
"devDependencies": { "devDependencies": {
"@lingui/babel-preset-react": "^2.7.0", "@lingui/babel-preset-react": "^2.9.0",
"@lingui/cli": "^2.7.0", "@lingui/cli": "^2.9.0",
"@lingui/loader": "^2.7.0", "@lingui/loader": "^2.9.0",
"@umijs/preset-react": "^1.4.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-plugin-dev-expression": "^0.2.1", "babel-plugin-dev-expression": "^0.2.1",
"babel-plugin-import": "^1.10.0", "babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-macros": "^2.4.2", "cross-env": "^7.0.0",
"babel-plugin-module-resolver": "^3.1.1", "eslint": "^7.0.0",
"cross-env": "^5.2.0", "eslint-config-react-app": "^5.0.0",
"eslint": "^5.7.0", "eslint-plugin-flowtype": "^5.1.0",
"eslint-config-react-app": "^3.0.4", "eslint-plugin-import": "^2.18.0",
"eslint-plugin-flowtype": "^3.0.0", "eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-import": "^2.6.0", "eslint-plugin-react": "^7.14.0",
"eslint-plugin-jsx-a11y": "^6.1.1", "eslint-plugin-react-hooks": "^4.0.0",
"eslint-plugin-react": "^7.1.0", "husky": "^4.2.0",
"husky": "^1.1.1",
"less-vars-to-js": "^1.3.0", "less-vars-to-js": "^1.3.0",
"lint-staged": "^7.3.0", "lint-staged": "^10.0.0",
"mockjs": "^1.0.1-beta3", "mockjs": "^1.1.0",
"module": "^1.2.5", "module": "^1.2.5",
"prettier": "^1.14.3", "prettier": "^2.0.0",
"stylelint": "^9.6.0", "stylelint": "^13.2.0",
"stylelint-config-prettier": "^4.0.0", "stylelint-config-prettier": "^8.0.0",
"stylelint-config-standard": "^18.2.0", "stylelint-config-standard": "^20.0.0",
"umi": "^2.2.1", "typescript": "^3.8.3",
"umi-plugin-react": "^1.2.0" "umi": "^3.2.0"
}, },
"optionalDependencies": { "engines": {
"puppeteer": "^1.9.0" "node": ">= 10.0.0"
}, },
"lint-staged": { "lint-staged": {
"src/**/*.js": [ "src/**/*.js": [
@@ -94,18 +93,19 @@
"format": "minimal", "format": "minimal",
"extractBabelOptions": { "extractBabelOptions": {
"presets": [ "presets": [
"umi/babel" "@umijs/babel-preset-umi"
] ]
} }
}, },
"scripts": { "scripts": {
"analyze": "cross-env ANALYZE=1 umi build", "analyze": "cross-env ANALYZE=1 umi build",
"build": "umi build", "build": "umi build",
"check:model": "umi dva list model",
"lint:js": "eslint --ext .js src", "lint:js": "eslint --ext .js src",
"lint:style": "stylelint \"src/**/*.less\" --syntax less", "lint:style": "stylelint \"src/**/*.less\" --syntax less",
"start": "umi dev", "start": "umi dev",
"test": "cross-env BABELRC=none umi test", "test": "cross-env BABELRC=none umi test",
"prettier": "prettier --write 'src/**/*.(js|less)'", "prettier": "prettier --write 'src/**/*.{js,less}'",
"precommit": "lint-staged", "precommit": "lint-staged",
"add-locale": "lingui add-locale", "add-locale": "lingui add-locale",
"extract": "lingui extract", "extract": "lingui extract",
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<rect style="fill:#D80027;" width="512" height="512"/>
<polygon style="fill:#6DA544;" points="196.641,0 196.641,264.348 196.641,512 0,512 0,0 "/>
<circle style="fill:#FFDA44;" cx="196.641" cy="256" r="96"/>
<path style="fill:#D80027;" d="M142.638,208v60c0,29.823,24.178,54,54,54s54-24.178,54-54v-60H142.638z"/>
<path style="fill:#F0F0F0;" d="M196.638,286c-9.925,0-18-8.075-18-18v-24.001h36V268C214.638,277.925,206.563,286,196.638,286z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 973 B

+15 -3
View File
@@ -56,9 +56,21 @@ const transform = async ({ from, to, locales, outputPath }) => {
let res = key let res = key
let way = 'youdao' let way = 'youdao'
if (key.indexOf('/') !== 0) { if (key.indexOf('/') !== 0) {
res = await youdao({ const reg = '{([^{}]*)}'
q: key, const tasks = key
}) .match(new RegExp(`${reg}|((?<=(${reg}|^)).*?(?=(${reg}|$)))`, 'g'))
.map(item => {
if (new RegExp(reg).test(item)) {
return Promise.resolve(item)
}
return youdao({
q: item,
from,
to,
})
})
res = (await Promise.all(tasks)).join('')
} else { } else {
res = `/${to + key}` res = `/${to + key}`
way = 'link' way = 'link'
+4 -3
View File
@@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Dropdown, Button, Icon, Menu } from 'antd' import { BarsOutlined, DownOutlined } from '@ant-design/icons'
import { Dropdown, Button, Menu } from 'antd'
const DropOption = ({ const DropOption = ({
onMenuClick, onMenuClick,
@@ -17,8 +18,8 @@ const DropOption = ({
{...dropdownProps} {...dropdownProps}
> >
<Button style={{ border: 'none', ...buttonStyle }}> <Button style={{ border: 'none', ...buttonStyle }}>
<Icon style={{ marginRight: 2 }} type="bars" /> <BarsOutlined style={{ marginRight: 2 }} />
<Icon type="down" /> <DownOutlined />
</Button> </Button>
</Dropdown> </Dropdown>
) )
+21
View File
@@ -0,0 +1,21 @@
import React from 'react';
import { TooltipProps } from 'antd/lib/tooltip';
export interface EllipsisTooltipProps extends TooltipProps {
title?: undefined;
overlayStyle?: undefined;
}
export interface EllipsisProps {
tooltip?: boolean | EllipsisTooltipProps;
length?: number;
lines?: number;
style?: React.CSSProperties;
className?: string;
fullWidthRecognition?: boolean;
}
export function getStrFullLength(str: string): number;
export function cutStrByFullLength(str: string, maxLength: number): string;
export default class Ellipsis extends React.Component<EllipsisProps, any> {}
+270
View File
@@ -0,0 +1,270 @@
import React, { Component } from 'react';
import { Tooltip } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
/* eslint react/no-did-mount-set-state: 0 */
/* eslint no-param-reassign: 0 */
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
const TooltipOverlayStyle = {
overflowWrap: 'break-word',
wordWrap: 'break-word',
};
export const getStrFullLength = (str = '') =>
str.split('').reduce((pre, cur) => {
const charCode = cur.charCodeAt(0);
if (charCode >= 0 && charCode <= 128) {
return pre + 1;
}
return pre + 2;
}, 0);
export const cutStrByFullLength = (str = '', maxLength) => {
let showLength = 0;
return str.split('').reduce((pre, cur) => {
const charCode = cur.charCodeAt(0);
if (charCode >= 0 && charCode <= 128) {
showLength += 1;
} else {
showLength += 2;
}
if (showLength <= maxLength) {
return pre + cur;
}
return pre;
}, '');
};
const getTooltip = ({ tooltip, overlayStyle, title, children }) => {
if (tooltip) {
const props = tooltip === true ? { overlayStyle, title } : { ...tooltip, overlayStyle, title };
return <Tooltip {...props}>{children}</Tooltip>;
}
return children;
};
const EllipsisText = ({ text, length, tooltip, fullWidthRecognition, ...other }) => {
if (typeof text !== 'string') {
throw new Error('Ellipsis children must be string.');
}
const textLength = fullWidthRecognition ? getStrFullLength(text) : text.length;
if (textLength <= length || length < 0) {
return <span {...other}>{text}</span>;
}
const tail = '...';
let displayText;
if (length - tail.length <= 0) {
displayText = '';
} else {
displayText = fullWidthRecognition ? cutStrByFullLength(text, length) : text.slice(0, length);
}
const spanAttrs = tooltip ? {} : { ...other };
return getTooltip({
tooltip,
overlayStyle: TooltipOverlayStyle,
title: text,
children: (
<span {...spanAttrs}>
{displayText}
{tail}
</span>
),
});
};
export default class Ellipsis extends Component {
state = {
text: '',
targetCount: 0,
};
componentDidMount() {
if (this.node) {
this.computeLine();
}
}
componentDidUpdate(perProps) {
const { lines } = this.props;
if (lines !== perProps.lines) {
this.computeLine();
}
}
computeLine = () => {
const { lines } = this.props;
if (lines && !isSupportLineClamp) {
const text = this.shadowChildren.innerText || this.shadowChildren.textContent;
const lineHeight = parseInt(getComputedStyle(this.root).lineHeight, 10);
const targetHeight = lines * lineHeight;
this.content.style.height = `${targetHeight}px`;
const totalHeight = this.shadowChildren.offsetHeight;
const shadowNode = this.shadow.firstChild;
if (totalHeight <= targetHeight) {
this.setState({
text,
targetCount: text.length,
});
return;
}
// bisection
const len = text.length;
const mid = Math.ceil(len / 2);
const count = this.bisection(targetHeight, mid, 0, len, text, shadowNode);
this.setState({
text,
targetCount: count,
});
}
};
bisection = (th, m, b, e, text, shadowNode) => {
const suffix = '...';
let mid = m;
let end = e;
let begin = b;
shadowNode.innerHTML = text.substring(0, mid) + suffix;
let sh = shadowNode.offsetHeight;
if (sh <= th) {
shadowNode.innerHTML = text.substring(0, mid + 1) + suffix;
sh = shadowNode.offsetHeight;
if (sh > th || mid === begin) {
return mid;
}
begin = mid;
if (end - begin === 1) {
mid = 1 + begin;
} else {
mid = Math.floor((end - begin) / 2) + begin;
}
return this.bisection(th, mid, begin, end, text, shadowNode);
}
if (mid - 1 < 0) {
return mid;
}
shadowNode.innerHTML = text.substring(0, mid - 1) + suffix;
sh = shadowNode.offsetHeight;
if (sh <= th) {
return mid - 1;
}
end = mid;
mid = Math.floor((end - begin) / 2) + begin;
return this.bisection(th, mid, begin, end, text, shadowNode);
};
handleRoot = n => {
this.root = n;
};
handleContent = n => {
this.content = n;
};
handleNode = n => {
this.node = n;
};
handleShadow = n => {
this.shadow = n;
};
handleShadowChildren = n => {
this.shadowChildren = n;
};
render() {
const { text, targetCount } = this.state;
const {
children,
lines,
length,
className,
tooltip,
fullWidthRecognition,
...restProps
} = this.props;
const cls = classNames(styles.ellipsis, className, {
[styles.lines]: lines && !isSupportLineClamp,
[styles.lineClamp]: lines && isSupportLineClamp,
});
if (!lines && !length) {
return (
<span className={cls} {...restProps}>
{children}
</span>
);
}
// length
if (!lines) {
return (
<EllipsisText
className={cls}
length={length}
text={children || ''}
tooltip={tooltip}
fullWidthRecognition={fullWidthRecognition}
{...restProps}
/>
);
}
const id = `antd-pro-ellipsis-${`${new Date().getTime()}${Math.floor(Math.random() * 100)}`}`;
// support document.body.style.webkitLineClamp
if (isSupportLineClamp) {
const style = `#${id}{-webkit-line-clamp:${lines};-webkit-box-orient: vertical;}`;
const node = (
<div id={id} className={cls} {...restProps}>
<style>{style}</style>
{children}
</div>
);
return getTooltip({
tooltip,
overlayStyle: TooltipOverlayStyle,
title: children,
children: node,
});
}
const childNode = (
<span ref={this.handleNode}>
{targetCount > 0 && text.substring(0, targetCount)}
{targetCount > 0 && targetCount < text.length && '...'}
</span>
);
return (
<div {...restProps} ref={this.handleRoot} className={cls}>
<div ref={this.handleContent}>
{getTooltip({
tooltip,
overlayStyle: TooltipOverlayStyle,
title: text,
children: childNode,
})}
<div className={styles.shadow} ref={this.handleShadowChildren}>
{children}
</div>
<div className={styles.shadow} ref={this.handleShadow}>
<span>{text}</span>
</div>
</div>
</div>
);
}
}
+24
View File
@@ -0,0 +1,24 @@
.ellipsis {
display: inline-block;
width: 100%;
overflow: hidden;
word-break: break-all;
}
.lines {
position: relative;
.shadow {
position: absolute;
z-index: -999;
display: block;
color: transparent;
opacity: 0;
}
}
.lineClamp {
position: relative;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
}
+17
View File
@@ -0,0 +1,17 @@
---
title: Ellipsis
subtitle: 文本自动省略号
cols: 1
order: 10
---
文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
## API
| 参数 | 说明 | 类型 | 默认值 |
| -------------------- | ------------------------------------------------ | ------- | ------ |
| tooltip | 移动到文本展示完整内容的提示 | boolean | - |
| length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - |
| lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1` |
| fullWidthRecognition | 是否将全角字符的长度视为 2 来计算字符串长度 | boolean | - |
+13
View File
@@ -0,0 +1,13 @@
import { getStrFullLength, cutStrByFullLength } from './index';
describe('test calculateShowLength', () => {
it('get full length', () => {
expect(getStrFullLength('一二,a,')).toEqual(8);
});
it('cut str by full length', () => {
expect(cutStrByFullLength('一二,a,', 7)).toEqual('一二,a');
});
it('cut str when length small', () => {
expect(cutStrByFullLength('一22三', 5)).toEqual('一22');
});
});
+1 -3
View File
@@ -6,7 +6,7 @@ const FilterItem = ({ label = '', children }) => {
const labelArray = label.split('') const labelArray = label.split('')
return ( return (
<div className={styles.filterItem}> <div className={styles.filterItem}>
{labelArray.length > 0 ? ( {labelArray.length > 0 && (
<div className={styles.labelWrap}> <div className={styles.labelWrap}>
{labelArray.map((item, index) => ( {labelArray.map((item, index) => (
<span className="labelText" key={index}> <span className="labelText" key={index}>
@@ -14,8 +14,6 @@ const FilterItem = ({ label = '', children }) => {
</span> </span>
))} ))}
</div> </div>
) : (
''
)} )}
<div className={styles.item}>{children}</div> <div className={styles.item}>{children}</div>
</div> </div>
+14
View File
@@ -0,0 +1,14 @@
import React from 'react';
export interface GlobalFooterProps {
links?: Array<{
key?: string;
title: React.ReactNode;
href: string;
blankTarget?: boolean;
}>;
copyright?: React.ReactNode;
style?: React.CSSProperties;
className?: string;
}
export default class GlobalFooter extends React.Component<GlobalFooterProps, any> {}
+28
View File
@@ -0,0 +1,28 @@
import React from 'react';
import classNames from 'classnames';
import styles from './index.less';
const GlobalFooter = ({ className, links, copyright }) => {
const clsString = classNames(styles.globalFooter, className);
return (
<footer className={clsString}>
{links && (
<div className={styles.links}>
{links.map(link => (
<a
key={link.key}
title={link.key}
target={link.blankTarget ? '_blank' : '_self'}
href={link.href}
>
{link.title}
</a>
))}
</div>
)}
{copyright && <div className={styles.copyright}>{copyright}</div>}
</footer>
);
};
export default GlobalFooter;
+29
View File
@@ -0,0 +1,29 @@
@import '~antd/lib/style/themes/default.less';
.globalFooter {
margin: 48px 0 24px 0;
padding: 0 16px;
text-align: center;
.links {
margin-bottom: 8px;
a {
color: @text-color-secondary;
transition: all 0.3s;
&:not(:last-child) {
margin-right: 40px;
}
&:hover {
color: @text-color;
}
}
}
.copyright {
color: @text-color-secondary;
font-size: @font-size-base;
}
}
+15
View File
@@ -0,0 +1,15 @@
---
title: GlobalFooter
subtitle: 全局页脚
cols: 1
order: 7
---
页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。
## API
| 参数 | 说明 | 类型 | 默认值 |
| --------- | -------- | ---------------------------------------------------------------- | ------ |
| links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - |
| copyright | 版权信息 | ReactNode | - |
+20 -17
View File
@@ -1,34 +1,37 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Breadcrumb, Icon } from 'antd' import { Breadcrumb } from 'antd'
import Link from 'umi/navlink' import { Link, withRouter } from 'umi'
import withRouter from 'umi/withRouter'
import { withI18n } from '@lingui/react' import { withI18n } from '@lingui/react'
import { pathMatchRegexp, queryAncestors } from 'utils' import iconMap from 'utils/iconMap'
const { pathToRegexp } = require('path-to-regexp')
import { queryAncestors } from 'utils'
import styles from './Bread.less' import styles from './Bread.less'
@withI18n() @withI18n()
@withRouter @withRouter
class Bread extends PureComponent { class Bread extends PureComponent {
generateBreadcrumbs = paths => { generateBreadcrumbs = (paths) => {
return paths.map((item, key) => { return paths.map((item, key) => {
const content = ( const content = item && (
<Fragment> <Fragment>
{item.icon ? ( {item.icon && (
<Icon type={item.icon} style={{ marginRight: 4 }} /> <span style={{ marginRight: 4 }}>{iconMap[item.icon]}</span>
) : null} )}
{item.name} {item.name}
</Fragment> </Fragment>
) )
return ( return (
<Breadcrumb.Item key={key}> item && (
{paths.length - 1 !== key ? ( <Breadcrumb.Item key={key}>
<Link to={item.route || '#'}>{content}</Link> {paths.length - 1 !== key ? (
) : ( <Link to={item.route || '#'}>{content}</Link>
content ) : (
)} content
</Breadcrumb.Item> )}
</Breadcrumb.Item>
)
) )
}) })
} }
@@ -37,7 +40,7 @@ class Bread extends PureComponent {
// Find a route that matches the pathname. // Find a route that matches the pathname.
const currentRoute = routeList.find( const currentRoute = routeList.find(
_ => _.route && pathMatchRegexp(_.route, location.pathname) (_) => _.route && pathToRegexp(_.route).exec(location.pathname)
) )
// Find the breadcrumb navigation of the current route match and all its ancestors. // Find the breadcrumb navigation of the current route match and all its ancestors.
+12 -16
View File
@@ -1,7 +1,13 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Menu, Icon, Layout, Avatar, Popover, Badge, List } from 'antd' import { Menu, Layout, Avatar, Popover, Badge, List } from 'antd'
import { Ellipsis } from 'ant-design-pro' import { Ellipsis } from 'components'
import {
BellOutlined,
RightOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons'
import { Trans, withI18n } from '@lingui/react' import { Trans, withI18n } from '@lingui/react'
import { setLocale } from 'utils' import { setLocale } from 'utils'
import moment from 'moment' import moment from 'moment'
@@ -85,7 +91,7 @@ class Header extends PureComponent {
trigger="click" trigger="click"
key="notifications" key="notifications"
overlayClassName={styles.notificationPopover} overlayClassName={styles.notificationPopover}
getPopupContainer={() => document.querySelector('#layoutHeader')} getPopupContainer={() => document.querySelector('#primaryLayout')}
content={ content={
<div className={styles.notification}> <div className={styles.notification}>
<List <List
@@ -104,11 +110,7 @@ class Header extends PureComponent {
} }
description={moment(item.date).fromNow()} description={moment(item.date).fromNow()}
/> />
<Icon <RightOutlined style={{ fontSize: 10, color: '#ccc' }} />
style={{ fontSize: 10, color: '#ccc' }}
type="right"
theme="outlined"
/>
</List.Item> </List.Item>
)} )}
/> />
@@ -129,7 +131,7 @@ class Header extends PureComponent {
offset={[-10, 10]} offset={[-10, 10]}
className={styles.iconButton} className={styles.iconButton}
> >
<Icon className={styles.iconFont} type="bell" /> <BellOutlined className={styles.iconFont} />
</Badge> </Badge>
</Popover> </Popover>
) )
@@ -146,14 +148,8 @@ class Header extends PureComponent {
className={styles.button} className={styles.button}
onClick={onCollapseChange.bind(this, !collapsed)} onClick={onCollapseChange.bind(this, !collapsed)}
> >
<Icon {collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
type={classnames({
'menu-unfold': collapsed,
'menu-fold': !collapsed,
})}
/>
</div> </div>
<div className={styles.rightContainer}>{rightContent}</div> <div className={styles.rightContainer}>{rightContent}</div>
</Layout.Header> </Layout.Header>
) )
+1 -1
View File
@@ -16,7 +16,7 @@
top: 0; top: 0;
right: 0; right: 0;
width: ~'calc(100% - 256px)'; width: ~'calc(100% - 256px)';
z-index: 9; z-index: 29;
transition: width 0.2s; transition: width 0.2s;
&.collapsed { &.collapsed {
+17 -17
View File
@@ -1,14 +1,10 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Menu, Icon } from 'antd' import { Menu } from 'antd'
import Navlink from 'umi/navlink' import { NavLink, withRouter } from 'umi'
import withRouter from 'umi/withRouter' import { pathToRegexp } from 'path-to-regexp'
import { import { arrayToTree, queryAncestors } from 'utils'
arrayToTree, import iconMap from 'utils/iconMap'
queryAncestors,
pathMatchRegexp,
addLangPrefix,
} from 'utils'
import store from 'store' import store from 'store'
const { SubMenu } = Menu const { SubMenu } = Menu
@@ -46,7 +42,7 @@ class SiderMenu extends PureComponent {
key={item.id} key={item.id}
title={ title={
<Fragment> <Fragment>
{item.icon && <Icon type={item.icon} />} {item.icon && iconMap[item.icon]}
<span>{item.name}</span> <span>{item.name}</span>
</Fragment> </Fragment>
} }
@@ -57,10 +53,10 @@ class SiderMenu extends PureComponent {
} }
return ( return (
<Menu.Item key={item.id}> <Menu.Item key={item.id}>
<Navlink to={addLangPrefix(item.route) || '#'}> <NavLink to={item.route || '#'}>
{item.icon && <Icon type={item.icon} />} {item.icon && iconMap[item.icon]}
<span>{item.name}</span> <span>{item.name}</span>
</Navlink> </NavLink>
</Menu.Item> </Menu.Item>
) )
}) })
@@ -81,7 +77,7 @@ class SiderMenu extends PureComponent {
// Find a menu that matches the pathname. // Find a menu that matches the pathname.
const currentMenu = menus.find( const currentMenu = menus.find(
_ => _.route && pathMatchRegexp(_.route, location.pathname) _ => _.route && pathToRegexp(_.route).exec(location.pathname)
) )
// Find the key that should be selected according to the current menu. // Find the key that should be selected according to the current menu.
@@ -89,13 +85,17 @@ class SiderMenu extends PureComponent {
? queryAncestors(menus, currentMenu, 'menuParentId').map(_ => _.id) ? queryAncestors(menus, currentMenu, 'menuParentId').map(_ => _.id)
: [] : []
const menuProps = collapsed
? {}
: {
openKeys: this.state.openKeys,
}
return ( return (
<Menu <Menu
mode="inline" mode="inline"
theme={theme} theme={theme}
onOpenChange={this.onOpenChange} onOpenChange={this.onOpenChange}
openKeys={this.state.openKeys}
inlineCollapsed={collapsed}
selectedKeys={selectedKeys} selectedKeys={selectedKeys}
onClick={ onClick={
isMobile isMobile
@@ -104,6 +104,7 @@ class SiderMenu extends PureComponent {
} }
: undefined : undefined
} }
{...menuProps}
> >
{this.generateMenus(menuTree)} {this.generateMenus(menuTree)}
</Menu> </Menu>
@@ -115,7 +116,6 @@ SiderMenu.propTypes = {
menus: PropTypes.array, menus: PropTypes.array,
theme: PropTypes.string, theme: PropTypes.string,
isMobile: PropTypes.bool, isMobile: PropTypes.bool,
collapsed: PropTypes.bool,
onCollapseChange: PropTypes.func, onCollapseChange: PropTypes.func,
} }
+7 -6
View File
@@ -1,7 +1,8 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Icon, Switch, Layout } from 'antd' import { Switch, Layout } from 'antd'
import { withI18n, Trans } from '@lingui/react' import { withI18n, Trans } from '@lingui/react'
import { BulbOutlined } from '@ant-design/icons'
import ScrollBar from '../ScrollBar' import ScrollBar from '../ScrollBar'
import { config } from 'utils' import { config } from 'utils'
import SiderMenu from './Menu' import SiderMenu from './Menu'
@@ -28,19 +29,19 @@ class Sider extends PureComponent {
trigger={null} trigger={null}
collapsible collapsible
collapsed={collapsed} collapsed={collapsed}
onBreakpoint={isMobile ? null : onCollapseChange} onBreakpoint={!isMobile && onCollapseChange}
className={styles.sider} className={styles.sider}
> >
<div className={styles.brand}> <div className={styles.brand}>
<div className={styles.logo}> <div className={styles.logo}>
<img alt="logo" src={config.logoPath} /> <img alt="logo" src={config.logoPath} />
{collapsed ? null : <h1>{config.siteName}</h1>} {!collapsed && <h1>{config.siteName}</h1>}
</div> </div>
</div> </div>
<div className={styles.menuContainer}> <div className={styles.menuContainer}>
<ScrollBar <ScrollBar
option={{ options={{
// Disabled horizontal scrolling, https://github.com/utatti/perfect-scrollbar#options // Disabled horizontal scrolling, https://github.com/utatti/perfect-scrollbar#options
suppressScrollX: true, suppressScrollX: true,
}} }}
@@ -54,10 +55,10 @@ class Sider extends PureComponent {
/> />
</ScrollBar> </ScrollBar>
</div> </div>
{collapsed ? null : ( {!collapsed && (
<div className={styles.switchTheme}> <div className={styles.switchTheme}>
<span> <span>
<Icon type="bulb" /> <BulbOutlined />
<Trans>Switch Theme</Trans> <Trans>Switch Theme</Trans>
</span> </span>
<Switch <Switch
+1 -1
View File
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import classNames from 'classnames' import classNames from 'classnames'
import styles from './Loader.less' import styles from './Loader.less'
const Loader = ({ spinning = true, fullScreen }) => { const Loader = ({ spinning = false, fullScreen }) => {
return ( return (
<div <div
className={classNames(styles.loader, { className={classNames(styles.loader, {
+3 -1
View File
@@ -3,7 +3,9 @@ import FilterItem from './FilterItem'
import DropOption from './DropOption' import DropOption from './DropOption'
import Loader from './Loader' import Loader from './Loader'
import ScrollBar from './ScrollBar' import ScrollBar from './ScrollBar'
import GlobalFooter from './GlobalFooter'
import Ellipsis from './Ellipsis'
import * as MyLayout from './Layout/index.js' import * as MyLayout from './Layout/index.js'
import Page from './Page' import Page from './Page'
export { MyLayout, Editor, FilterItem, DropOption, Loader, Page, ScrollBar } export { MyLayout, Editor, GlobalFooter, Ellipsis, FilterItem, DropOption, Loader, Page, ScrollBar }
+2 -2
View File
@@ -1,12 +1,12 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'dva' import { connect } from 'umi'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { Loader } from 'components' import { Loader } from 'components'
import { queryLayout } from 'utils' import { queryLayout } from 'utils'
import NProgress from 'nprogress' import NProgress from 'nprogress'
import config from 'utils/config' import config from 'utils/config'
import withRouter from 'umi/withRouter' import { withRouter } from 'umi'
import PublicLayout from './PublicLayout' import PublicLayout from './PublicLayout'
import PrimaryLayout from './PrimaryLayout' import PrimaryLayout from './PrimaryLayout'
+17 -18
View File
@@ -2,15 +2,16 @@
/* global document */ /* global document */
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import withRouter from 'umi/withRouter' import { withRouter } from 'umi'
import { connect } from 'dva' import { connect } from 'umi'
import { MyLayout } from 'components' import { MyLayout, GlobalFooter } from 'components'
import { BackTop, Layout, Drawer } from 'antd' import { BackTop, Layout, Drawer } from 'antd'
import { GlobalFooter } from 'ant-design-pro'
import { enquireScreen, unenquireScreen } from 'enquire-js' import { enquireScreen, unenquireScreen } from 'enquire-js'
import { config, pathMatchRegexp, langFromPath } from 'utils' const { pathToRegexp } = require("path-to-regexp")
import { config, getLocale } from 'utils'
import Error from '../pages/404' import Error from '../pages/404'
import styles from './PrimaryLayout.less' import styles from './PrimaryLayout.less'
import store from 'store'
const { Content } = Layout const { Content } = Layout
const { Header, Bread, Sider } = MyLayout const { Header, Bread, Sider } = MyLayout
@@ -46,32 +47,30 @@ class PrimaryLayout extends PureComponent {
render() { render() {
const { app, location, dispatch, children } = this.props const { app, location, dispatch, children } = this.props
const { const { theme, collapsed, notifications } = app
user, const user = store.get('user') || {}
theme, const permissions = store.get('permissions') || {}
routeList, const routeList = store.get('routeList') || []
collapsed,
permissions,
notifications,
} = app
const { isMobile } = this.state const { isMobile } = this.state
const { onCollapseChange } = this const { onCollapseChange } = this
// Localized route name. // Localized route name.
const lang = getLocale()
const newRouteList = const newRouteList =
langFromPath(location.pathname) === 'zh' lang !== 'en'
? routeList.map(item => { ? routeList.map(item => {
const { zhName, ...other } = item const { name, ...other } = item
return { return {
...other, ...other,
name: zhName, name: (item[lang] || {}).name || name,
} }
}) })
: routeList : routeList
// Find a route that matches the pathname. // Find a route that matches the pathname.
const currentRoute = newRouteList.find( const currentRoute = newRouteList.find(
_ => _.route && pathMatchRegexp(_.route, location.pathname) _ => _.route && pathToRegexp(_.route).exec(location.pathname)
) )
// Query whether you have permission to enter this page // Query whether you have permission to enter this page
@@ -145,7 +144,7 @@ class PrimaryLayout extends PureComponent {
</Content> </Content>
<BackTop <BackTop
className={styles.backTop} className={styles.backTop}
target={() => document.querySelector('#primaryLayout>div')} target={() => document.querySelector('#primaryLayout')}
/> />
<GlobalFooter <GlobalFooter
className={styles.footer} className={styles.footer}
+19 -13
View File
@@ -1,17 +1,21 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import withRouter from 'umi/withRouter' import { withRouter } from 'umi'
import { LocaleProvider } from 'antd' import { ConfigProvider } from 'antd'
import { I18nProvider } from '@lingui/react' import { I18nProvider } from '@lingui/react'
import { langFromPath, defaultLanguage } from 'utils' import { getLocale } from 'utils'
const { i18n } = require('../../src/utils/config')
import zh_CN from 'antd/lib/locale-provider/zh_CN' import zh_CN from 'antd/lib/locale-provider/zh_CN'
import en_US from 'antd/lib/locale-provider/en_US' import en_US from 'antd/lib/locale-provider/en_US'
import pt_BR from 'antd/lib/locale-provider/pt_BR'
import BaseLayout from './BaseLayout' import BaseLayout from './BaseLayout'
const languages = { const languages = {
zh: zh_CN, zh: zh_CN,
en: en_US, en: en_US,
'pt-br': pt_BR,
} }
const { defaultLanguage } = i18n
@withRouter @withRouter
class Layout extends Component { class Layout extends Component {
@@ -22,18 +26,18 @@ class Layout extends Component {
language = defaultLanguage language = defaultLanguage
componentDidMount() { componentDidMount() {
const language = langFromPath(this.props.location.pathname) const language = getLocale()
this.language = language this.language = language
this.loadCatalog(language) language && this.loadCatalog(language)
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const language = langFromPath(nextProps.location.pathname) const language = getLocale()
const preLanguage = this.language const preLanguage = this.language
const { catalogs } = nextState const { catalogs } = nextState
if (preLanguage !== language && !catalogs[language]) { if (preLanguage !== language && !catalogs[language]) {
this.loadCatalog(language) language && this.loadCatalog(language)
this.language = language this.language = language
return false return false
} }
@@ -43,8 +47,10 @@ class Layout extends Component {
} }
loadCatalog = async language => { loadCatalog = async language => {
const catalog = await import(/* webpackMode: "lazy", webpackChunkName: "i18n-[index]" */ const catalog = await import(
`@lingui/loader!../locales/${language}/messages.json`) /* webpackMode: "lazy", webpackChunkName: "i18n-[index]" */
`@lingui/loader!../locales/${language}/messages.json`
)
this.setState(state => ({ this.setState(state => ({
catalogs: { catalogs: {
@@ -55,19 +61,19 @@ class Layout extends Component {
} }
render() { render() {
const { location, children } = this.props const { children } = this.props
const { catalogs } = this.state const { catalogs } = this.state
let language = langFromPath(location.pathname) let language = getLocale()
// If the language pack is not loaded or is loading, use the default language // If the language pack is not loaded or is loading, use the default language
if (!catalogs[language]) language = defaultLanguage if (!catalogs[language]) language = defaultLanguage
return ( return (
<LocaleProvider locale={languages[language]}> <ConfigProvider locale={languages[language]}>
<I18nProvider language={language} catalogs={catalogs}> <I18nProvider language={language} catalogs={catalogs}>
<BaseLayout>{children}</BaseLayout> <BaseLayout>{children}</BaseLayout>
</I18nProvider> </I18nProvider>
</LocaleProvider> </ConfigProvider>
) )
} }
} }
+54
View File
@@ -0,0 +1,54 @@
{
"/dashboard": "/dashboard",
"Add Param": "Add Parametro",
"Address": "Endereço",
"Age": "Ano",
"Are you sure delete this record?": "Tem certeza de excluir este registro?",
"Author": "Autor",
"Avatar": "Avatar",
"Categories": "Categorias",
"Clear notifications": "limpar notificações",
"Comments": "Comentarios",
"Create": "Criar",
"Create User": "Criar Usuário",
"CreateTime": "CreateTime",
"Dark": "Escuro",
"Delete": "Deletar",
"Email": "Email",
"Female": "Feminino",
"Gender": "Genero",
"Hi,": "Olá,",
"Image": "Imagem",
"Light": "Claro",
"Male": "masculino",
"Name": "Nome",
"NickName": "NickName",
"Not Found": "Não Encontrado",
"Operation": "Operation",
"Params": "Parametros",
"Password": "Senha",
"Phone": "Fone",
"Pick an address": "Escolha um endereço",
"Please pick an address": "Por favor, escolha um endereço",
"Publised": "Publicado",
"Publish Date": "Data de publicação",
"Reset": "Reset",
"Search": "procurar",
"Search Name": "Search Name",
"Send": "Enviar",
"Sign in": "Sign in",
"Sign out": "Sign out",
"Switch Theme": "Trocar tema",
"Tags": "Tags",
"The input is not valid E-mail!": "Não é um E-mail valido!",
"The input is not valid phone!": "Não é um telefone Valido!",
"Title": "Titulo",
"Total {total} Items": "Total {total} Items",
"Unpublished": "Não publicado",
"Update": "Atualizar",
"Update User": "Atualizar Usuário",
"Username": "Usuário",
"Views": "visualizações",
"Visibility": "Visibilidade",
"You have viewed all notifications.": "Você visualizou todas as notificações."
}
+34 -43
View File
@@ -1,21 +1,28 @@
/* global window */ /* global window */
import { router } from 'utils' import { history } from 'umi'
import { stringify } from 'qs' import { stringify } from 'qs'
import store from 'store' import store from 'store'
const { pathToRegexp } = require("path-to-regexp")
import { ROLE_TYPE } from 'utils/constant' import { ROLE_TYPE } from 'utils/constant'
import { queryLayout, pathMatchRegexp } from 'utils' import { queryLayout } from 'utils'
import { CANCEL_REQUEST_MESSAGE } from 'utils/constant' import { CANCEL_REQUEST_MESSAGE } from 'utils/constant'
import { queryRouteList, logoutUser, queryUserInfo } from 'api' import api from 'api'
import config from 'config' import config from 'config'
const { queryRouteList, logoutUser, queryUserInfo } = api
const goDashboard = () => {
if (pathToRegexp(['/', '/login']).exec(window.location.pathname)) {
history.push({
pathname: '/dashboard',
})
}
}
export default { export default {
namespace: 'app', namespace: 'app',
state: { state: {
user: {},
permissions: {
visit: [],
},
routeList: [ routeList: [
{ {
id: '1', id: '1',
@@ -41,6 +48,9 @@ export default {
], ],
}, },
subscriptions: { subscriptions: {
setup({ dispatch }) {
dispatch({ type: 'query' })
},
setupHistory({ dispatch, history }) { setupHistory({ dispatch, history }) {
history.listen(location => { history.listen(location => {
dispatch({ dispatch({
@@ -65,16 +75,17 @@ export default {
}) })
}) })
}, },
setup({ dispatch }) {
dispatch({ type: 'query' })
},
}, },
effects: { effects: {
*query({ payload }, { call, put, select }) { *query({ payload }, { call, put, select }) {
const { success, user } = yield call(queryUserInfo, payload) // store isInit to prevent query trigger by refresh
const isInit = store.get('isInit')
if (isInit) {
goDashboard()
return
}
const { locationPathname } = yield select(_ => _.app) const { locationPathname } = yield select(_ => _.app)
const { success, user } = yield call(queryUserInfo, payload)
if (success && user) { if (success && user) {
const { list } = yield call(queryRouteList) const { list } = yield call(queryRouteList)
const { permissions } = user const { permissions } = user
@@ -96,21 +107,13 @@ export default {
return cases.every(_ => _) return cases.every(_ => _)
}) })
} }
yield put({ store.set('routeList', routeList)
type: 'updateState', store.set('permissions', permissions)
payload: { store.set('user', user)
user, store.set('isInit', true)
permissions, goDashboard()
routeList,
},
})
if (pathMatchRegexp('/login', window.location.pathname)) {
router.push({
pathname: '/dashboard',
})
}
} else if (queryLayout(config.layouts, locationPathname) !== 'public') { } else if (queryLayout(config.layouts, locationPathname) !== 'public') {
router.push({ history.push({
pathname: '/login', pathname: '/login',
search: stringify({ search: stringify({
from: locationPathname, from: locationPathname,
@@ -122,22 +125,10 @@ export default {
*signOut({ payload }, { call, put }) { *signOut({ payload }, { call, put }) {
const data = yield call(logoutUser) const data = yield call(logoutUser)
if (data.success) { if (data.success) {
yield put({ store.set('routeList', [])
type: 'updateState', store.set('permissions', { visit: [] })
payload: { store.set('user', {})
user: {}, store.set('isInit', false)
permissions: { visit: [] },
menu: [
{
id: '1',
icon: 'laptop',
name: 'Dashboard',
zhName: '仪表盘',
router: '/dashboard',
},
],
},
})
yield put({ type: 'query' }) yield put({ type: 'query' })
} else { } else {
throw data throw data
+2 -2
View File
@@ -1,12 +1,12 @@
import React from 'react' import React from 'react'
import { Icon } from 'antd' import { FrownOutlined } from '@ant-design/icons'
import { Page } from 'components' import { Page } from 'components'
import styles from './404.less' import styles from './404.less'
const Error = () => ( const Error = () => (
<Page inner> <Page inner>
<div className={styles.error}> <div className={styles.error}>
<Icon type="frown-o" /> <FrownOutlined />
<h1>404 Not Found</h1> <h1>404 Not Found</h1>
</div> </div>
</Page> </Page>
-27
View File
@@ -1,27 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { ResponsiveContainer } from 'recharts'
import styles from './Container.less'
const Container = ({
children,
ratio = 5 / 2,
minHeight = 250,
maxHeight = 350,
}) => (
<div className={styles.container} style={{ minHeight, maxHeight }}>
<div style={{ marginTop: `${100 / ratio}%` || '100%' }} />
<div className={styles.content} style={{ minHeight, maxHeight }}>
<ResponsiveContainer>{children}</ResponsiveContainer>
</div>
</div>
)
Container.propTypes = {
children: PropTypes.element.isRequired,
ratio: PropTypes.number,
minHeight: PropTypes.number,
maxHeight: PropTypes.number,
}
export default Container
-20
View File
@@ -1,20 +0,0 @@
.container {
width: 100%;
position: relative;
display: inline-block;
:global {
.recharts-responsive-container {
width: e('calc(100% + 56px)') !important;
margin-left: -32px;
}
}
}
.content {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import AdSense from 'react-adsense' import AdSense from 'react-adsense'
import { Link } from 'react-router' import { Link } from 'umi'
import DynamicChartComponent from './DynamicChartComponent.js' import DynamicChartComponent from './DynamicChartComponent.js'
const MainPageComponent = () => { const MainPageComponent = () => {
+1 -1
View File
@@ -38,7 +38,7 @@ function Browser({ data }) {
pagination={false} pagination={false}
showHeader={false} showHeader={false}
columns={columns} columns={columns}
rowKey={(record, key) => key} rowKey='name'
dataSource={data} dataSource={data}
/> />
) )
+1 -1
View File
@@ -54,7 +54,7 @@ function Comments({ data }) {
pagination={false} pagination={false}
showHeader={false} showHeader={false}
columns={columns} columns={columns}
rowKey={(record, key) => key} rowKey='avatar'
dataSource={data.filter((item, key) => key < 3)} dataSource={data.filter((item, key) => key < 3)}
/> />
</div> </div>
+1 -1
View File
@@ -73,7 +73,7 @@ function Completed({ data }) {
return ( return (
<div className={styles.tooltip}> <div className={styles.tooltip}>
<p className={styles.tiptitle}>{content.label}</p> <p className={styles.tiptitle}>{content.label}</p>
<ul>{list}</ul> {content.payload && <ul>{list}</ul>}
</div> </div>
) )
}} }}
+7 -3
View File
@@ -1,9 +1,11 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Icon, Card } from 'antd' import { Card } from 'antd'
import CountUp from 'react-countup' import CountUp from 'react-countup'
import iconMap from 'utils/iconMap'
import styles from './numberCard.less' import styles from './numberCard.less'
function NumberCard({ icon, color, title, number, countUp }) { function NumberCard({ icon, color, title, number, countUp }) {
return ( return (
<Card <Card
@@ -11,7 +13,9 @@ function NumberCard({ icon, color, title, number, countUp }) {
bordered={false} bordered={false}
bodyStyle={{ padding: 10 }} bodyStyle={{ padding: 10 }}
> >
<Icon className={styles.iconWarp} style={{ color }} type={icon} /> <span className={styles.iconWarp} style={{ color }}>
{iconMap[icon]}
</span>
<div className={styles.content}> <div className={styles.content}>
<p className={styles.title}>{title || 'No Title'}</p> <p className={styles.title}>{title || 'No Title'}</p>
<p className={styles.number}> <p className={styles.number}>
@@ -22,7 +26,7 @@ function NumberCard({ icon, color, title, number, countUp }) {
useEasing useEasing
useGrouping useGrouping
separator="," separator=","
{...countUp || {}} {...(countUp || {})}
/> />
</p> </p>
</div> </div>
@@ -53,7 +53,7 @@ function RecentSales({ data }) {
<Table <Table
pagination={false} pagination={false}
columns={columns} columns={columns}
rowKey={(record, key) => key} rowKey='id'
dataSource={data.filter((item, key) => key < 5)} dataSource={data.filter((item, key) => key < 5)}
/> />
</div> </div>
+1 -1
View File
@@ -73,7 +73,7 @@ function Sales({ data }) {
return ( return (
<div className={styles.tooltip}> <div className={styles.tooltip}>
<p className={styles.tiptitle}>{content.label}</p> <p className={styles.tiptitle}>{content.label}</p>
<ul>{list}</ul> {content.payload && <ul>{list}</ul>}
</div> </div>
) )
}} }}
+5 -6
View File
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'dva' import { connect } from 'umi'
import { Row, Col, Card } from 'antd' import { Row, Col, Card } from 'antd'
import { Color } from 'utils' import { Color } from 'utils'
import { Page, ScrollBar } from 'components' import { Page, ScrollBar } from 'components'
@@ -17,6 +17,7 @@ import {
User, User,
} from './components' } from './components'
import styles from './index.less' import styles from './index.less'
import store from 'store'
const bodyStyle = { const bodyStyle = {
bodyStyle: { bodyStyle: {
@@ -26,14 +27,14 @@ const bodyStyle = {
} }
@connect(({ app, dashboard, loading }) => ({ @connect(({ app, dashboard, loading }) => ({
avatar: app.user.avatar,
username: app.user.username,
dashboard, dashboard,
loading, loading,
})) }))
class Dashboard extends PureComponent { class Dashboard extends PureComponent {
render() { render() {
const { avatar, username, dashboard, loading } = this.props const userDetail = store.get('user')
const { avatar, username } = userDetail
const { dashboard, loading } = this.props
const { const {
weather, weather,
sales, sales,
@@ -154,8 +155,6 @@ class Dashboard extends PureComponent {
} }
Dashboard.propTypes = { Dashboard.propTypes = {
avatar: PropTypes.string,
username: PropTypes.string,
dashboard: PropTypes.object, dashboard: PropTypes.object,
loading: PropTypes.object, loading: PropTypes.object,
} }
+11 -10
View File
@@ -1,9 +1,12 @@
import { parse } from 'qs' import { parse } from 'qs'
import modelExtend from 'dva-model-extend' import modelExtend from 'dva-model-extend'
import { queryDashboard, queryWeather } from 'api' import api from 'api'
import { pathMatchRegexp } from 'utils' const { pathToRegexp } = require("path-to-regexp")
import { model } from 'utils/model' import { model } from 'utils/model'
const { queryDashboard, queryWeather } = api
const avatar = '//cdn.antd-admin.zuiidea.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236.jpeg'
export default modelExtend(model, { export default modelExtend(model, {
namespace: 'dashboard', namespace: 'dashboard',
state: { state: {
@@ -11,12 +14,11 @@ export default modelExtend(model, {
city: '深圳', city: '深圳',
temperature: '30', temperature: '30',
name: '晴', name: '晴',
icon: '//s5.sencdn.com/web/icons/3d_50/2.png', icon: '//cdn.antd-admin.zuiidea.com/sun.png',
}, },
sales: [], sales: [],
quote: { quote: {
avatar: avatar,
'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
}, },
numbers: [], numbers: [],
recentSales: [], recentSales: [],
@@ -25,16 +27,15 @@ export default modelExtend(model, {
browser: [], browser: [],
cpu: {}, cpu: {},
user: { user: {
avatar: avatar,
'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
}, },
}, },
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen(({ pathname }) => { history.listen(({ pathname }) => {
if ( if (
pathMatchRegexp('/dashboard', pathname) || pathToRegexp('/dashboard').exec(pathname) ||
pathMatchRegexp('/', pathname) pathToRegexp('/').exec(pathname)
) { ) {
dispatch({ type: 'query' }) dispatch({ type: 'query' })
dispatch({ type: 'queryWeather' }) dispatch({ type: 'queryWeather' })
@@ -60,7 +61,7 @@ export default modelExtend(model, {
city: data.location.name, city: data.location.name,
temperature: data.now.temperature, temperature: data.now.temperature,
name: data.now.text, name: data.now.text,
icon: `//s5.sencdn.com/web/icons/3d_50/${data.now.code}.png`, icon: `//cdn.antd-admin.zuiidea.com/web/icons/3d_50/${data.now.code}.png`,
} }
yield put({ yield put({
type: 'updateState', type: 'updateState',
@@ -1,11 +1,11 @@
import React from 'react' import { Component } from 'react'
import { Editor } from 'components' import { Editor, Page } from 'components'
import { convertToRaw } from 'draft-js' import { convertToRaw } from 'draft-js'
import { Row, Col, Card } from 'antd' import { Row, Col, Card } from 'antd'
import draftToHtml from 'draftjs-to-html' import draftToHtml from 'draftjs-to-html'
import draftToMarkdown from 'draftjs-to-markdown' import draftToMarkdown from 'draftjs-to-markdown'
export default class EditorPage extends React.Component { export default class EditorPage extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
@@ -26,17 +26,18 @@ export default class EditorPage extends React.Component {
md: 24, md: 24,
style: { style: {
marginBottom: 32, marginBottom: 32,
}, }
} }
const textareaStyle = { const textareaStyle = {
minHeight: 496, minHeight: 496,
width: '100%', width: '100%',
background: '#f7f7f7', background: '#f7f7f7',
borderColor: '#F1F1F1', borderColor: '#F1F1F1',
padding: '16px 8px', padding: '16px 8px'
} }
return ( return (
<div className="content-inner"> <Page inner>
<Row gutter={32}> <Row gutter={32}>
<Col {...colProps}> <Col {...colProps}>
<Card title="Editor" style={{ overflow: 'visible' }}> <Card title="Editor" style={{ overflow: 'visible' }}>
@@ -98,7 +99,7 @@ export default class EditorPage extends React.Component {
</Card> </Card>
</Col> </Col>
</Row> </Row>
</div> </Page>
) )
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import Redirect from 'umi/redirect' import { Redirect } from 'umi'
import { withI18n } from '@lingui/react' import { withI18n } from '@lingui/react'
@withI18n() @withI18n()
+22 -43
View File
@@ -1,38 +1,31 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'dva' import { connect } from 'umi'
import { Button, Row, Form, Icon, Input } from 'antd' import { Button, Row, Input, Form } from 'antd'
import { GlobalFooter } from 'ant-design-pro' import { GlobalFooter } from 'components'
import { GithubOutlined } from '@ant-design/icons'
import { Trans, withI18n } from '@lingui/react' import { Trans, withI18n } from '@lingui/react'
import { setLocale } from 'utils' import { setLocale } from 'utils'
import config from 'utils/config' import config from 'utils/config'
import styles from './index.less' import styles from './index.less'
const FormItem = Form.Item const FormItem = Form.Item
@withI18n() @withI18n()
@connect(({ loading }) => ({ loading })) @connect(({ loading, dispatch }) => ({ loading, dispatch }))
@Form.create()
class Login extends PureComponent { class Login extends PureComponent {
handleOk = () => {
const { dispatch, form } = this.props
const { validateFieldsAndScroll } = form
validateFieldsAndScroll((errors, values) => {
if (errors) {
return
}
dispatch({ type: 'login/login', payload: values })
})
}
render() { render() {
const { loading, form, i18n } = this.props const { dispatch, loading, i18n } = this.props
const { getFieldDecorator } = form
const handleOk = values => {
dispatch({ type: 'login/login', payload: values })
}
let footerLinks = [ let footerLinks = [
{ {
key: 'github', key: 'github',
title: <Icon type="github" />, title: <GithubOutlined />,
href: 'https://github.com/zuiidea/antd-admin', href: 'https://github.com/zuiidea/antd-admin',
blankTarget: true, blankTarget: true,
}, },
@@ -56,46 +49,32 @@ class Login extends PureComponent {
<img alt="logo" src={config.logoPath} /> <img alt="logo" src={config.logoPath} />
<span>{config.siteName}</span> <span>{config.siteName}</span>
</div> </div>
<form> <Form
<FormItem hasFeedback> onFinish={handleOk}
{getFieldDecorator('username', { >
rules: [ <FormItem name="username"
{ rules={[{ required: true }]} hasFeedback>
required: true,
},
],
})(
<Input <Input
onPressEnter={this.handleOk}
placeholder={i18n.t`Username`} placeholder={i18n.t`Username`}
/> />
)}
</FormItem> </FormItem>
<FormItem hasFeedback> <FormItem name="password"
{getFieldDecorator('password', { rules={[{ required: true }]} hasFeedback>
rules: [
{
required: true,
},
],
})(
<Input <Input
type="password" type="password"
onPressEnter={this.handleOk}
placeholder={i18n.t`Password`} placeholder={i18n.t`Password`}
/> />
)}
</FormItem> </FormItem>
<Row> <Row>
<Button <Button
type="primary" type="primary"
onClick={this.handleOk} htmlType="submit"
loading={loading.effects.login} loading={loading.effects.login}
> >
<Trans>Sign in</Trans> <Trans>Sign in</Trans>
</Button> </Button>
<p> <p>
<span> <span className="margin-right">
<Trans>Username</Trans> <Trans>Username</Trans>
:guest :guest
</span> </span>
@@ -105,7 +84,7 @@ class Login extends PureComponent {
</span> </span>
</p> </p>
</Row> </Row>
</form> </Form>
</div> </div>
<div className={styles.footer}> <div className={styles.footer}>
<GlobalFooter links={footerLinks} copyright={config.copyright} /> <GlobalFooter links={footerLinks} copyright={config.copyright} />
+17 -7
View File
@@ -1,11 +1,21 @@
import { router, pathMatchRegexp } from 'utils' import { history } from 'umi'
import { loginUser } from 'api' const { pathToRegexp } = require("path-to-regexp")
import api from 'api'
const { loginUser } = api
export default { export default {
namespace: 'login', namespace: 'login',
state: {}, state: {},
// subscriptions: {
// setup({ dispatch, history }) {
// history.listen(location => {
// if (pathToRegexp('/login').exec(location.pathname)) {
// }
// })
// },
// },
effects: { effects: {
*login({ payload }, { put, call, select }) { *login({ payload }, { put, call, select }) {
const data = yield call(loginUser, payload) const data = yield call(loginUser, payload)
@@ -13,11 +23,11 @@ export default {
if (data.success) { if (data.success) {
const { from } = locationQuery const { from } = locationQuery
yield put({ type: 'app/query' }) yield put({ type: 'app/query' })
if (!pathMatchRegexp('/login', from)) { if (!pathToRegexp('/login').exec(from)) {
if (from === '/') router.push('/dashboard') if (['', '/'].includes(from)) history.push('/dashboard')
else router.push(from) else history.push(from)
} else { } else {
router.push('/dashboard') history.push('/dashboard')
} }
} else { } else {
throw data throw data
+1 -1
View File
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { Table, Avatar } from 'antd' import { Table, Avatar } from 'antd'
import { withI18n } from '@lingui/react' import { withI18n } from '@lingui/react'
import { Ellipsis } from 'ant-design-pro' import { Ellipsis } from 'components'
import styles from './List.less' import styles from './List.less'
@withI18n() @withI18n()
+24 -17
View File
@@ -1,8 +1,8 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'dva' import { connect } from 'umi'
import { Tabs } from 'antd' import { Tabs } from 'antd'
import { router } from 'utils' import { history } from 'umi'
import { stringify } from 'qs' import { stringify } from 'qs'
import { withI18n } from '@lingui/react' import { withI18n } from '@lingui/react'
import { Page } from 'components' import { Page } from 'components'
@@ -18,17 +18,28 @@ const EnumPostStatus = {
@withI18n() @withI18n()
@connect(({ post, loading }) => ({ post, loading })) @connect(({ post, loading }) => ({ post, loading }))
class Post extends PureComponent { class Post extends PureComponent {
render() { handleTabClick = key => {
const { post, loading, location, i18n } = this.props const { pathname } = this.props.location
history.push({
pathname,
search: stringify({
status: key,
}),
})
}
get listProps() {
const { post, loading, location } = this.props
const { list, pagination } = post const { list, pagination } = post
const { query, pathname } = location const { query, pathname } = location
const listProps = { return {
pagination, pagination,
dataSource: list, dataSource: list,
loading: loading.effects['post/query'], loading: loading.effects['post/query'],
onChange(page) { onChange(page) {
router.push({ history.push({
pathname, pathname,
search: stringify({ search: stringify({
...query, ...query,
@@ -38,15 +49,11 @@ class Post extends PureComponent {
}) })
}, },
} }
}
const handleTabClick = key => { render() {
router.push({ const { location, i18n } = this.props
pathname, const { query } = location
search: stringify({
status: key,
}),
})
}
return ( return (
<Page inner> <Page inner>
@@ -56,19 +63,19 @@ class Post extends PureComponent {
? String(EnumPostStatus.UNPUBLISH) ? String(EnumPostStatus.UNPUBLISH)
: String(EnumPostStatus.PUBLISHED) : String(EnumPostStatus.PUBLISHED)
} }
onTabClick={handleTabClick} onTabClick={this.handleTabClick}
> >
<TabPane <TabPane
tab={i18n.t`Publised`} tab={i18n.t`Publised`}
key={String(EnumPostStatus.PUBLISHED)} key={String(EnumPostStatus.PUBLISHED)}
> >
<List {...listProps} /> <List {...this.listProps} />
</TabPane> </TabPane>
<TabPane <TabPane
tab={i18n.t`Unpublished`} tab={i18n.t`Unpublished`}
key={String(EnumPostStatus.UNPUBLISH)} key={String(EnumPostStatus.UNPUBLISH)}
> >
<List {...listProps} /> <List {...this.listProps} />
</TabPane> </TabPane>
</Tabs> </Tabs>
</Page> </Page>
+5 -3
View File
@@ -1,15 +1,17 @@
import modelExtend from 'dva-model-extend' import modelExtend from 'dva-model-extend'
import { queryPostList } from 'api' import api from 'api'
import { pathMatchRegexp } from 'utils' const { pathToRegexp } = require("path-to-regexp")
import { pageModel } from 'utils/model' import { pageModel } from 'utils/model'
const { queryPostList } = api
export default modelExtend(pageModel, { export default modelExtend(pageModel, {
namespace: 'post', namespace: 'post',
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen(location => { history.listen(location => {
if (pathMatchRegexp('/post', location.pathname)) { if (pathToRegexp('/post').exec(location.pathname)) {
dispatch({ dispatch({
type: 'query', type: 'query',
payload: { payload: {
+73 -69
View File
@@ -1,19 +1,9 @@
import React from 'react' import React from 'react'
import { request } from 'utils' import { request } from 'utils'
import { apiPrefix } from 'utils/config' import { apiPrefix } from 'utils/config'
import { import { Row, Col, Select, Form, Input, Button, List, Tag, Checkbox } from 'antd'
Row,
Col,
Select,
Input,
Button,
List,
Tag,
Form,
Icon,
Checkbox,
} from 'antd'
import classnames from 'classnames' import classnames from 'classnames'
import { CloseOutlined } from '@ant-design/icons'
import { Trans } from '@lingui/react' import { Trans } from '@lingui/react'
import api from '@/services/api' import api from '@/services/api'
import { Page } from 'components' import { Page } from 'components'
@@ -46,8 +36,8 @@ const requests = Object.values(api).map(item => {
}) })
let uuid = 2 let uuid = 2
@Form.create()
class RequestPage extends React.Component { class RequestPage extends React.Component {
formRef = React.createRef()
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
@@ -62,15 +52,17 @@ class RequestPage extends React.Component {
handleRequest = () => { handleRequest = () => {
const { method, url } = this.state const { method, url } = this.state
this.props.form.validateFields((err, values) => { this.formRef.current.validateFields()
if (!err) { .then(values => {
// values: { check[1]: true, key[1]: 'username', value[1]: 'admin' }
const params = {} const params = {}
if (values.key) { for (let i in values) {
values.key.forEach((item, index) => { if (i.startsWith('check')) {
if (item && values.check[index]) { const index = i.match(/check\[(\d+)\]/)[1]
params[item] = values.value[index] const key = values[`key[${index}]`]
} params[key] = values[`value[${index}]`]
}) }
} }
request({ method, url, data: params }).then(data => { request({ method, url, data: params }).then(data => {
@@ -78,8 +70,23 @@ class RequestPage extends React.Component {
result: JSON.stringify(data), result: JSON.stringify(data),
}) })
}) })
} })
}) .catch(errorInfo => {
console.log(errorInfo)
/*
errorInfo:
{
values: {
username: 'username',
password: 'password',
},
errorFields: [
{ password: ['username'], errors: ['Please input your Password!'] },
],
outOfDate: false,
}
*/
})
} }
handleClickListItem = ({ method, url }) => { handleClickListItem = ({ method, url }) => {
@@ -127,7 +134,6 @@ class RequestPage extends React.Component {
render() { render() {
const { result, url, method, keys, visible } = this.state const { result, url, method, keys, visible } = this.state
const { getFieldDecorator } = this.props.form
return ( return (
<Page inner> <Page inner>
@@ -196,53 +202,51 @@ class RequestPage extends React.Component {
<Trans>Send</Trans> <Trans>Send</Trans>
</Button> </Button>
</Row> </Row>
<Form ref={this.formRef} name="control-ref" >
<div
className={classnames(styles.paramsBlock, {
[styles.hideParams]: !visible,
})}
>
{keys.map((key, index) => (
<Row
gutter={8}
type="flex"
justify="start"
align="middle"
key={key}
>
<Col style={{ marginTop: 8 }}>
<Form.Item name={`check[${key}]`} valuePropName="checked">
<Checkbox defaultChecked />
</Form.Item>
</Col>
<Col style={{ marginTop: 8 }}>
<Form.Item name={`key[${key}]`}>
<Input placeholder="Key" />
</Form.Item>
</Col>
<Col style={{ marginTop: 8 }}>
<Form.Item name={`value[${key}]`}>
<Input placeholder="Value" />
</Form.Item>
</Col>
<Col style={{ marginTop: 8 }}>
<CloseOutlined
onClick={this.handleRemoveField.bind(this, key)}
style={{ cursor: 'pointer' }}
/>
</Col>
</Row>
))}
<div <Row style={{ marginTop: 8 }}>
className={classnames(styles.paramsBlock, { <Button onClick={this.handleAddField}>
[styles.hideParams]: !visible, <Trans>Add Param</Trans>
})} </Button>
>
{keys.map((key, index) => (
<Row
gutter={8}
type="flex"
justify="start"
align="middle"
key={key}
>
<Col style={{ marginTop: 8 }}>
{getFieldDecorator(`check[${key}]`, {
initialValue: true,
})(<Checkbox defaultChecked />)}
</Col>
<Col style={{ marginTop: 8 }}>
{getFieldDecorator(`key[${key}]`)(
<Input placeholder="Key" />
)}
</Col>
<Col style={{ marginTop: 8 }}>
{getFieldDecorator(`value[${key}]`)(
<Input placeholder="Value" />
)}
</Col>
<Col style={{ marginTop: 8 }}>
<Icon
onClick={this.handleRemoveField.bind(this, key)}
style={{ cursor: 'pointer' }}
type="close"
theme="outlined"
/>
</Col>
</Row> </Row>
))} </div>
</Form>
<Row style={{ marginTop: 8 }}>
<Button onClick={this.handleAddField}>
<Trans>Add Param</Trans>
</Button>
</Row>
</div>
<div className={styles.result}>{result}</div> <div className={styles.result}>{result}</div>
</Col> </Col>
</Row> </Row>
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'dva' import { connect } from 'umi'
import { Page } from 'components' import { Page } from 'components'
import styles from './index.less' import styles from './index.less'
@@ -1,5 +1,7 @@
import { pathMatchRegexp } from 'utils' const { pathToRegexp } = require("path-to-regexp")
import { queryUser } from 'api' import api from 'api'
const { queryUser } = api
export default { export default {
namespace: 'userDetail', namespace: 'userDetail',
@@ -11,7 +13,7 @@ export default {
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen(({ pathname }) => { history.listen(({ pathname }) => {
const match = pathMatchRegexp('/user/:id', pathname) const match = pathToRegexp('/user/:id').exec(pathname)
if (match) { if (match) {
dispatch({ type: 'query', payload: { id: match[1] } }) dispatch({ type: 'query', payload: { id: match[1] } })
} }
+79 -96
View File
@@ -1,10 +1,10 @@
/* global document */ import React, { Component } from 'react'
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import moment from 'moment' import moment from 'moment'
import { FilterItem } from 'components' import { FilterItem } from 'components'
import { Trans, withI18n } from '@lingui/react'
import { Form, Button, Row, Col, DatePicker, Input, Cascader } from 'antd' import { Trans } from '@lingui/react'
import { Button, Row, Col, DatePicker, Form, Input, Cascader } from 'antd'
import city from 'utils/city' import city from 'utils/city'
const { Search } = Input const { Search } = Input
@@ -23,12 +23,12 @@ const TwoColProps = {
xl: 96, xl: 96,
} }
@withI18n() class Filter extends Component {
@Form.create() formRef = React.createRef()
class Filter extends PureComponent {
handleFields = fields => { handleFields = fields => {
const { createTime } = fields const { createTime } = fields
if (createTime.length) { if (createTime && createTime.length) {
fields.createTime = [ fields.createTime = [
moment(createTime[0]).format('YYYY-MM-DD'), moment(createTime[0]).format('YYYY-MM-DD'),
moment(createTime[1]).format('YYYY-MM-DD'), moment(createTime[1]).format('YYYY-MM-DD'),
@@ -38,19 +38,14 @@ class Filter extends PureComponent {
} }
handleSubmit = () => { handleSubmit = () => {
const { onFilterChange, form } = this.props const { onFilterChange } = this.props
const { getFieldsValue } = form const values = this.formRef.current.getFieldsValue()
const fields = this.handleFields(values)
let fields = getFieldsValue()
fields = this.handleFields(fields)
onFilterChange(fields) onFilterChange(fields)
} }
handleReset = () => { handleReset = () => {
const { form } = this.props const fields = this.formRef.current.getFieldsValue()
const { getFieldsValue, setFieldsValue } = form
const fields = getFieldsValue()
for (let item in fields) { for (let item in fields) {
if ({}.hasOwnProperty.call(fields, item)) { if ({}.hasOwnProperty.call(fields, item)) {
if (fields[item] instanceof Array) { if (fields[item] instanceof Array) {
@@ -60,22 +55,19 @@ class Filter extends PureComponent {
} }
} }
} }
setFieldsValue(fields) this.formRef.current.setFieldsValue(fields)
this.handleSubmit() this.handleSubmit()
} }
handleChange = (key, values) => { handleChange = (key, values) => {
const { form, onFilterChange } = this.props const { onFilterChange } = this.props
const { getFieldsValue } = form let fields = this.formRef.current.getFieldsValue()
let fields = getFieldsValue()
fields[key] = values fields[key] = values
fields = this.handleFields(fields) fields = this.handleFields(fields)
onFilterChange(fields) onFilterChange(fields)
} }
render() { render() {
const { onAdd, filter, form, i18n } = this.props const { onAdd, filter, i18n } = this.props
const { getFieldDecorator } = form
const { name, address } = filter const { name, address } = filter
let initialCreateTime = [] let initialCreateTime = []
@@ -87,86 +79,77 @@ class Filter extends PureComponent {
} }
return ( return (
<Row gutter={24}> <Form ref={this.formRef} name="control-ref" initialValues={{ name, address, createTime: initialCreateTime }}>
<Col {...ColProps} xl={{ span: 4 }} md={{ span: 8 }}> <Row gutter={24}>
{getFieldDecorator('name', { initialValue: name })( <Col {...ColProps} xl={{ span: 4 }} md={{ span: 8 }}>
<Search <Form.Item name="name">
placeholder={i18n.t`Search Name`} <Search
onSearch={this.handleSubmit} placeholder={i18n.t`Search Name`}
/> onSearch={this.handleSubmit}
)}
</Col>
<Col
{...ColProps}
xl={{ span: 4 }}
md={{ span: 8 }}
id="addressCascader"
>
{getFieldDecorator('address', { initialValue: address })(
<Cascader
style={{ width: '100%' }}
options={city}
placeholder={i18n.t`Please pick an address`}
onChange={this.handleChange.bind(this, 'address')}
getPopupContainer={() =>
document.getElementById('addressCascader')
}
/>
)}
</Col>
<Col
{...ColProps}
xl={{ span: 6 }}
md={{ span: 8 }}
sm={{ span: 12 }}
id="createTimeRangePicker"
>
<FilterItem label={i18n.t`CreateTime`}>
{getFieldDecorator('createTime', {
initialValue: initialCreateTime,
})(
<RangePicker
style={{ width: '100%' }}
onChange={this.handleChange.bind(this, 'createTime')}
getCalendarContainer={() => {
return document.getElementById('createTimeRangePicker')
}}
/> />
)} </Form.Item>
</FilterItem> </Col>
</Col> <Col
<Col {...ColProps}
{...TwoColProps} xl={{ span: 4 }}
xl={{ span: 10 }} md={{ span: 8 }}
md={{ span: 24 }} id="addressCascader"
sm={{ span: 24 }} >
> <Form.Item name="address">
<Row type="flex" align="middle" justify="space-between"> <Cascader
<div> style={{ width: '100%' }}
<Button options={city}
type="primary" placeholder={i18n.t`Please pick an address`}
className="margin-right" />
onClick={this.handleSubmit} </Form.Item>
> </Col>
<Trans>Search</Trans> <Col
{...ColProps}
xl={{ span: 6 }}
md={{ span: 8 }}
sm={{ span: 12 }}
id="createTimeRangePicker"
>
<FilterItem label={i18n.t`CreateTime`}>
<Form.Item name="createTime">
<RangePicker
style={{ width: '100%' }}
/>
</Form.Item>
</FilterItem>
</Col>
<Col
{...TwoColProps}
xl={{ span: 10 }}
md={{ span: 24 }}
sm={{ span: 24 }}
>
<Row type="flex" align="middle" justify="space-between">
<div>
<Button
type="primary" htmlType="submit"
className="margin-right"
onClick={this.handleSubmit}
>
<Trans>Search</Trans>
</Button>
<Button onClick={this.handleReset}>
<Trans>Reset</Trans>
</Button>
</div>
<Button type="ghost" onClick={onAdd}>
<Trans>Create</Trans>
</Button> </Button>
<Button onClick={this.handleReset}> </Row>
<Trans>Reset</Trans> </Col>
</Button> </Row>
</div> </Form>
<Button type="ghost" onClick={onAdd}>
<Trans>Create</Trans>
</Button>
</Row>
</Col>
</Row>
) )
} }
} }
Filter.propTypes = { Filter.propTypes = {
onAdd: PropTypes.func, onAdd: PropTypes.func,
form: PropTypes.object,
filter: PropTypes.object, filter: PropTypes.object,
onFilterChange: PropTypes.func, onFilterChange: PropTypes.func,
} }
+3 -9
View File
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { Table, Modal, Avatar } from 'antd' import { Table, Modal, Avatar } from 'antd'
import { DropOption } from 'components' import { DropOption } from 'components'
import { Trans, withI18n } from '@lingui/react' import { Trans, withI18n } from '@lingui/react'
import Link from 'umi/link' import { Link } from 'umi'
import styles from './List.less' import styles from './List.less'
const { confirm } = Modal const { confirm } = Modal
@@ -26,13 +26,7 @@ class List extends PureComponent {
} }
render() { render() {
const { const { onDeleteItem, onEditItem, i18n, ...tableProps } = this.props
handleMenuClick,
onDeleteItem,
onEditItem,
i18n,
...tableProps
} = this.props
const columns = [ const columns = [
{ {
@@ -92,7 +86,7 @@ class List extends PureComponent {
render: (text, record) => { render: (text, record) => {
return ( return (
<DropOption <DropOption
onMenuClick={e => handleMenuClick(record, e)} onMenuClick={e => this.handleMenuClick(record, e)}
menuOptions={[ menuOptions={[
{ key: '1', name: i18n.t`Update` }, { key: '1', name: i18n.t`Update` },
{ key: '2', name: i18n.t`Delete` }, { key: '2', name: i18n.t`Delete` },
+52 -105
View File
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Form, Input, InputNumber, Radio, Modal, Cascader } from 'antd' import { Form, Input, InputNumber, Radio, Modal, Cascader } from 'antd'
import { Trans, withI18n } from '@lingui/react' import { Trans } from '@lingui/react'
import city from 'utils/city' import city from 'utils/city'
const FormItem = Form.Item const FormItem = Form.Item
@@ -14,123 +14,70 @@ const formItemLayout = {
span: 14, span: 14,
}, },
} }
@withI18n()
@Form.create()
class UserModal extends PureComponent {
handleOk = () => {
const { item = {}, onOk, form } = this.props
const { validateFields, getFieldsValue } = form
validateFields(errors => { class UserModal extends PureComponent {
if (errors) { formRef = React.createRef()
return
} handleOk = () => {
const data = { const { item = {}, onOk } = this.props
...getFieldsValue(),
key: item.key, this.formRef.current.validateFields()
} .then(values => {
data.address = data.address.join(' ') const data = {
onOk(data) ...values,
}) key: item.key,
}
data.address = data.address.join(' ')
onOk(data)
})
.catch(errorInfo => {
console.log(errorInfo)
})
} }
render() { render() {
const { item = {}, onOk, form, i18n, ...modalProps } = this.props const { i18n, item = {}, onOk, form, ...modalProps } = this.props
const { getFieldDecorator } = form
return ( return (
<Modal {...modalProps} onOk={this.handleOk}> <Modal {...modalProps} onOk={this.handleOk}>
<Form layout="horizontal"> <Form ref={this.formRef} name="control-ref" initialValues={{ ...item, address: item.address && item.address.split(' ') }} layout="horizontal">
<FormItem label={i18n.t`Name`} hasFeedback {...formItemLayout}> <FormItem name='name' rules={[{ required: true }]}
{getFieldDecorator('name', { label={i18n.t`Name`} hasFeedback {...formItemLayout}>
initialValue: item.name, <Input />
rules: [
{
required: true,
},
],
})(<Input />)}
</FormItem> </FormItem>
<FormItem label={i18n.t`NickName`} hasFeedback {...formItemLayout}> <FormItem name='nickName' rules={[{ required: true }]}
{getFieldDecorator('nickName', { label={i18n.t`NickName`} hasFeedback {...formItemLayout}>
initialValue: item.nickName, <Input />
rules: [
{
required: true,
},
],
})(<Input />)}
</FormItem> </FormItem>
<FormItem label={i18n.t`Gender`} hasFeedback {...formItemLayout}> <FormItem name='isMale' rules={[{ required: true }]}
{getFieldDecorator('isMale', { label={i18n.t`Gender`} hasFeedback {...formItemLayout}>
initialValue: item.isMale, <Radio.Group>
rules: [ <Radio value>
{ <Trans>Male</Trans>
required: true, </Radio>
type: 'boolean', <Radio value={false}>
}, <Trans>Female</Trans>
], </Radio>
})( </Radio.Group>
<Radio.Group>
<Radio value>
<Trans>Male</Trans>
</Radio>
<Radio value={false}>
<Trans>Female</Trans>
</Radio>
</Radio.Group>
)}
</FormItem> </FormItem>
<FormItem label={i18n.t`Age`} hasFeedback {...formItemLayout}> <FormItem name='age' label={i18n.t`Age`} hasFeedback {...formItemLayout}>
{getFieldDecorator('age', { <InputNumber min={18} max={100} />
initialValue: item.age,
rules: [
{
required: true,
type: 'number',
},
],
})(<InputNumber min={18} max={100} />)}
</FormItem> </FormItem>
<FormItem label={i18n.t`Phone`} hasFeedback {...formItemLayout}> <FormItem name='phone' rules={[{ required: true, pattern: /^1[34578]\d{9}$/, message: i18n.t`The input is not valid phone!`, }]}
{getFieldDecorator('phone', { label={i18n.t`Phone`} hasFeedback {...formItemLayout}>
initialValue: item.phone, <Input />
rules: [
{
required: true,
pattern: /^1[34578]\d{9}$/,
message: i18n.t`The input is not valid phone!`,
},
],
})(<Input />)}
</FormItem> </FormItem>
<FormItem label={i18n.t`Email`} hasFeedback {...formItemLayout}> <FormItem name='email' rules={[{ required: true, pattern: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/, message: i18n.t`The input is not valid E-mail!`, }]}
{getFieldDecorator('email', { label={i18n.t`Email`} hasFeedback {...formItemLayout}>
initialValue: item.email, <Input />
rules: [
{
required: true,
pattern: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/,
message: i18n.t`The input is not valid E-mail!`,
},
],
})(<Input />)}
</FormItem> </FormItem>
<FormItem label={i18n.t`Address`} hasFeedback {...formItemLayout}> <FormItem name='address' rules={[{ required: true, }]}
{getFieldDecorator('address', { label={i18n.t`Address`} hasFeedback {...formItemLayout}>
initialValue: item.address && item.address.split(' '), <Cascader
rules: [ style={{ width: '100%' }}
{ options={city}
required: true, placeholder={i18n.t`Pick an address`}
}, />
],
})(
<Cascader
style={{ width: '100%' }}
options={city}
placeholder={i18n.t`Pick an address`}
/>
)}
</FormItem> </FormItem>
</Form> </Form>
</Modal> </Modal>
+70 -55
View File
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { router } from 'utils' import { history } from 'umi'
import { connect } from 'dva' import { connect } from 'umi'
import { Row, Col, Button, Popconfirm } from 'antd' import { Row, Col, Button, Popconfirm } from 'antd'
import { withI18n } from '@lingui/react' import { withI18n } from '@lingui/react'
import { Page } from 'components' import { Page } from 'components'
@@ -13,46 +13,62 @@ import Modal from './components/Modal'
@withI18n() @withI18n()
@connect(({ user, loading }) => ({ user, loading })) @connect(({ user, loading }) => ({ user, loading }))
class User extends PureComponent { class User extends PureComponent {
render() { handleRefresh = newQuery => {
const { location, dispatch, user, loading, i18n } = this.props const { location } = this.props
const { query, pathname } = location const { query, pathname } = location
const {
list,
pagination,
currentItem,
modalVisible,
modalType,
selectedRowKeys,
} = user
const handleRefresh = newQuery => { history.push({
router.push({ pathname,
pathname, search: stringify(
search: stringify( {
{ ...query,
...query, ...newQuery,
...newQuery, },
}, { arrayFormat: 'repeat' }
{ arrayFormat: 'repeat' } ),
), })
}
handleDeleteItems = () => {
const { dispatch, user } = this.props
const { list, pagination, selectedRowKeys } = user
dispatch({
type: 'user/multiDelete',
payload: {
ids: selectedRowKeys,
},
}).then(() => {
this.handleRefresh({
page:
list.length === selectedRowKeys.length && pagination.current > 1
? pagination.current - 1
: pagination.current,
}) })
} })
}
const modalProps = { get modalProps() {
const { dispatch, user, loading, i18n } = this.props
const { currentItem, modalVisible, modalType } = user
return {
i18n,
item: modalType === 'create' ? {} : currentItem, item: modalType === 'create' ? {} : currentItem,
visible: modalVisible, visible: modalVisible,
destroyOnClose: true,
maskClosable: false, maskClosable: false,
confirmLoading: loading.effects[`user/${modalType}`], confirmLoading: loading.effects[`user/${modalType}`],
title: `${ title: `${
modalType === 'create' ? i18n.t`Create User` : i18n.t`Update User` modalType === 'create' ? i18n.t`Create User` : i18n.t`Update User`
}`, }`,
wrapClassName: 'vertical-center-modal', centered: true,
onOk(data) { onOk: data => {
dispatch({ dispatch({
type: `user/${modalType}`, type: `user/${modalType}`,
payload: data, payload: data,
}).then(() => { }).then(() => {
handleRefresh() this.handleRefresh()
}) })
}, },
onCancel() { onCancel() {
@@ -61,23 +77,28 @@ class User extends PureComponent {
}) })
}, },
} }
}
const listProps = { get listProps() {
const { dispatch, user, loading } = this.props
const { list, pagination, selectedRowKeys } = user
return {
dataSource: list, dataSource: list,
loading: loading.effects['user/query'], loading: loading.effects['user/query'],
pagination, pagination,
onChange(page) { onChange: page => {
handleRefresh({ this.handleRefresh({
page: page.current, page: page.current,
pageSize: page.pageSize, pageSize: page.pageSize,
}) })
}, },
onDeleteItem(id) { onDeleteItem: id => {
dispatch({ dispatch({
type: 'user/delete', type: 'user/delete',
payload: id, payload: id,
}).then(() => { }).then(() => {
handleRefresh({ this.handleRefresh({
page: page:
list.length === 1 && pagination.current > 1 list.length === 1 && pagination.current > 1
? pagination.current - 1 ? pagination.current - 1
@@ -106,15 +127,20 @@ class User extends PureComponent {
}, },
}, },
} }
}
const filterProps = { get filterProps() {
const { location, dispatch, i18n } = this.props
const { query } = location
return {
i18n,
filter: { filter: {
...query, ...query,
}, },
onFilterChange(value) { onFilterChange: value => {
handleRefresh({ this.handleRefresh({
...value, ...value,
page: 1,
}) })
}, },
onAdd() { onAdd() {
@@ -126,26 +152,15 @@ class User extends PureComponent {
}) })
}, },
} }
}
const handleDeleteItems = () => { render() {
dispatch({ const { user } = this.props
type: 'user/multiDelete', const { selectedRowKeys } = user
payload: {
ids: selectedRowKeys,
},
}).then(() => {
handleRefresh({
page:
list.length === selectedRowKeys.length && pagination.current > 1
? pagination.current - 1
: pagination.current,
})
})
}
return ( return (
<Page inner> <Page inner>
<Filter {...filterProps} /> <Filter {...this.filterProps} />
{selectedRowKeys.length > 0 && ( {selectedRowKeys.length > 0 && (
<Row style={{ marginBottom: 24, textAlign: 'right', fontSize: 13 }}> <Row style={{ marginBottom: 24, textAlign: 'right', fontSize: 13 }}>
<Col> <Col>
@@ -153,7 +168,7 @@ class User extends PureComponent {
<Popconfirm <Popconfirm
title="Are you sure delete these items?" title="Are you sure delete these items?"
placement="left" placement="left"
onConfirm={handleDeleteItems} onConfirm={this.handleDeleteItems}
> >
<Button type="primary" style={{ marginLeft: 8 }}> <Button type="primary" style={{ marginLeft: 8 }}>
Remove Remove
@@ -162,8 +177,8 @@ class User extends PureComponent {
</Col> </Col>
</Row> </Row>
)} )}
<List {...listProps} /> <List {...this.listProps} />
{modalVisible && <Modal {...modalProps} />} <Modal {...this.modalProps} />
</Page> </Page>
) )
} }
+7 -6
View File
@@ -1,14 +1,15 @@
/* global window */
import modelExtend from 'dva-model-extend' import modelExtend from 'dva-model-extend'
import { pathMatchRegexp } from 'utils' const { pathToRegexp } = require("path-to-regexp")
import { import api from 'api'
import { pageModel } from 'utils/model'
const {
queryUserList, queryUserList,
createUser, createUser,
removeUser, removeUser,
updateUser, updateUser,
removeUserList, removeUserList,
} from 'api' } = api
import { pageModel } from 'utils/model'
export default modelExtend(pageModel, { export default modelExtend(pageModel, {
namespace: 'user', namespace: 'user',
@@ -23,7 +24,7 @@ export default modelExtend(pageModel, {
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen(location => { history.listen(location => {
if (pathMatchRegexp('/user', location.pathname)) { if (pathToRegexp('/user').exec(location.pathname)) {
const payload = location.query || { page: 1, pageSize: 10 } const payload = location.query || { page: 1, pageSize: 10 }
dispatch({ dispatch({
type: 'query', type: 'query',
+7 -2
View File
@@ -1,8 +1,13 @@
import { message } from 'antd' import { message } from 'antd'
export default { export default {
onError(e) { onError(e, a) {
e.preventDefault() e.preventDefault()
message.error(e.message) if (e.message) {
message.error(e.message)
} else {
/* eslint-disable */
console.error(e)
}
}, },
} }

Some files were not shown because too many files have changed in this diff Show More