├── .editorconfig
├── .env
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .stylelintrc.json
├── .travis.yml
├── .umirc.js
├── README.md
├── assets
└── standard.md
├── config
├── id_rsa.enc
└── theme.config.js
├── jest.config.js
├── manifest.json
├── mock
├── _utils.js
├── dashboard.js
├── post.js
├── route.js
└── user.js
├── package.json
├── public
├── america.svg
├── avatar.png
├── china.svg
├── favicon.ico
├── logo.svg
└── lu.jpg
├── scripts
└── translate.js
└── src
├── components
├── DropOption
│ ├── DropOption.js
│ └── package.json
├── Editor
│ ├── Editor.js
│ ├── Editor.less
│ └── package.json
├── FilterItem
│ ├── FilterItem.js
│ ├── FilterItem.less
│ └── package.json
├── Layout
│ ├── Bread.js
│ ├── Bread.less
│ ├── Header.js
│ ├── Header.less
│ ├── Menu.js
│ ├── Sider.js
│ ├── Sider.less
│ └── index.js
├── Loader
│ ├── Loader.js
│ ├── Loader.less
│ └── package.json
├── Page
│ ├── Page.js
│ ├── Page.less
│ └── package.json
├── ScrollBar
│ ├── index.js
│ └── index.less
└── index.js
├── e2e
└── login.e2e.js
├── layouts
├── BaseLayout.js
├── BaseLayout.less
├── PrimaryLayout.js
├── PrimaryLayout.less
├── PublicLayout.js
└── index.js
├── locales
└── zh
│ └── messages.json
├── models
└── app.js
├── pages
├── .umi
│ ├── DvaContainer.js
│ ├── initDva.js
│ ├── initHistory.js
│ ├── polyfills.js
│ ├── router.js
│ └── umi.js
├── 404.js
├── 404.less
├── UIElement
│ └── editor
│ │ └── index.js
├── auth.js
├── chart
│ ├── Container.js
│ ├── Container.less
│ ├── ECharts
│ │ ├── AirportCoordComponent.js
│ │ ├── BubbleGradientComponent.js
│ │ ├── CalendarComponent.js
│ │ ├── ChartAPIComponent.js
│ │ ├── ChartShowLoadingComponent.js
│ │ ├── ChartWithEventComponent.js
│ │ ├── DynamicChartComponent.js
│ │ ├── EchartsComponent.js
│ │ ├── GCalendarComponent.js
│ │ ├── GaugeComponent.js
│ │ ├── GraphComponent.js
│ │ ├── LiquidfillComponent.js
│ │ ├── LunarCalendarComponent.js
│ │ ├── MainPageComponent.js
│ │ ├── MapChartComponent.js
│ │ ├── ModuleLoadChartComponent.js
│ │ ├── SimpleChartComponent.js
│ │ ├── ThemeChartComponent.js
│ │ ├── TransparentBar3DComPonent.js
│ │ ├── TreemapComponent.js
│ │ ├── index.js
│ │ ├── index.less
│ │ └── theme
│ │ │ ├── macarons.js
│ │ │ └── shine.js
│ ├── Recharts
│ │ ├── AreaChartComponent.js
│ │ ├── BarChartComponent.js
│ │ ├── Container.js
│ │ ├── Container.less
│ │ ├── LineChartComponent.js
│ │ ├── ReChartsComponent.js
│ │ ├── index.js
│ │ └── index.less
│ └── highCharts
│ │ ├── HighChartsComponent.js
│ │ ├── HighMoreComponent.js
│ │ ├── HighmapsComponent.js
│ │ ├── HighstockComponent.js
│ │ ├── index.js
│ │ ├── index.less
│ │ └── mapdata
│ │ └── europe.js
├── dashboard
│ ├── components
│ │ ├── browser.js
│ │ ├── browser.less
│ │ ├── comments.js
│ │ ├── comments.less
│ │ ├── completed.js
│ │ ├── completed.less
│ │ ├── cpu.js
│ │ ├── cpu.less
│ │ ├── index.js
│ │ ├── numberCard.js
│ │ ├── numberCard.less
│ │ ├── recentSales.js
│ │ ├── recentSales.less
│ │ ├── sales.js
│ │ ├── sales.less
│ │ ├── user-background.png
│ │ ├── user.js
│ │ ├── user.less
│ │ ├── village-profile.js
│ │ ├── village-profile.less
│ │ ├── weather.js
│ │ └── weather.less
│ ├── index.js
│ ├── index.less
│ ├── model.js
│ └── services
│ │ ├── dashboard.js
│ │ └── weather.js
├── dict
│ ├── components
│ │ ├── Filter.js
│ │ ├── List.js
│ │ ├── List.less
│ │ └── Modal.js
│ ├── index.js
│ └── model.js
├── folk
│ ├── category
│ │ ├── components
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ └── Modal.js
│ │ ├── index.js
│ │ └── model.js
│ ├── categorycontent
│ │ ├── $id
│ │ │ ├── index.js
│ │ │ ├── index.less
│ │ │ └── models
│ │ │ │ └── detail.js
│ │ ├── components
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ └── Modal.js
│ │ ├── index.js
│ │ └── model.js
│ ├── cemetery
│ │ ├── $id
│ │ │ ├── index.js
│ │ │ ├── index.less
│ │ │ └── models
│ │ │ │ └── detail.js
│ │ ├── components
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ └── Modal.js
│ │ ├── index.js
│ │ └── model.js
│ ├── people
│ │ ├── $id
│ │ │ ├── index.js
│ │ │ ├── index.less
│ │ │ └── models
│ │ │ │ └── detail.js
│ │ ├── components
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ ├── Modal.js
│ │ │ └── RelationshipModal.js
│ │ ├── index.js
│ │ └── model.js
│ ├── relationship
│ │ ├── components
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ └── Modal.js
│ │ ├── index.js
│ │ └── model.js
│ └── tree
│ │ ├── components
│ │ └── Modal.js
│ │ ├── index.js
│ │ ├── index.less
│ │ └── model.js
├── forgotpassword
│ ├── index.js
│ └── index.less
├── index.js
├── login
│ ├── index.js
│ ├── index.less
│ └── model.js
├── post
│ ├── components
│ │ ├── List.js
│ │ └── List.less
│ ├── index.js
│ └── model.js
├── region
│ ├── components
│ │ ├── Filter.js
│ │ ├── List.js
│ │ ├── List.less
│ │ └── Modal.js
│ ├── index.js
│ └── model.js
├── request
│ ├── index.js
│ └── index.less
├── security
│ ├── menu
│ │ ├── components
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ └── Modal.js
│ │ ├── index.js
│ │ └── model.js
│ ├── role
│ │ ├── components
│ │ │ ├── Filter.js
│ │ │ ├── List.js
│ │ │ ├── List.less
│ │ │ ├── Modal.js
│ │ │ └── RoleMenuModal.js
│ │ ├── index.js
│ │ └── model.js
│ └── rolemenu
│ │ ├── components
│ │ ├── Filter.js
│ │ ├── List.js
│ │ ├── List.less
│ │ └── Modal.js
│ │ ├── index.js
│ │ └── model.js
├── signup
│ ├── index.js
│ ├── index.less
│ └── model.js
├── user
│ ├── $id
│ │ ├── index.js
│ │ ├── index.less
│ │ └── models
│ │ │ └── detail.js
│ ├── components
│ │ ├── Filter.js
│ │ ├── List.js
│ │ ├── List.less
│ │ ├── MenuModal.js
│ │ ├── Modal.js
│ │ └── ResetPasswordModal.js
│ ├── index.js
│ └── model.js
└── village
│ ├── components
│ ├── Filter.js
│ ├── List.js
│ ├── List.less
│ └── Modal.js
│ ├── images
│ ├── index.js
│ └── model.js
│ ├── index.js
│ └── model.js
├── plugins
└── onError.js
├── services
├── api.js
└── index.js
├── themes
├── default.less
├── index.less
├── mixin.less
└── vars.less
└── utils
├── city.js
├── config.js
├── constant.js
├── index.js
├── index.test.js
├── model.js
├── request.js
└── theme.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | HOST=0.0.0.0
3 | PORT=7000
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/**/*-test.js
2 | src/public
3 | src/routes/chart/ECharts/theme
4 | src/routes/chart/highCharts/mapdata
5 | src/locales/_build/
6 | src/locales/**/*.js
7 | docs/**/*.js
8 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "rules": {
4 | "jsx-a11y/href-no-hash": "off",
5 | "no-console": "warn",
6 | "valid-jsdoc": "warn"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | node_modules
4 | npm-debug.log
5 | yarn-error.log
6 | yarn.lock
7 | package-lock.json
8 |
9 | # ide
10 | .idea
11 |
12 | # Mac General
13 | .DS_Store
14 | .AppleDouble
15 | .LSOverride
16 |
17 | # umi
18 | .umi
19 | .umi-production
20 |
21 | # jslingui
22 | src/locales/_build
23 | src/locales/**/*.js
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | *.svg
2 | *.ejs
3 | .DS_Store
4 | .umi
5 | .umi-production
6 | src/locales/**/*.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"],
3 | "rules": {
4 | "declaration-empty-line-before": null,
5 | "no-descending-specificity": null,
6 | "selector-pseudo-class-no-unknown": null,
7 | "selector-pseudo-element-colon-notation": null
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - node
4 | script:
5 | - npm run build
6 | before_install:
7 | - |
8 | if [ "$TRAVIS_BRANCH" = "develop" ]; then
9 | for prefixed_envvar in ${!DEV_*}; do
10 | eval export ${prefixed_envvar#DEV_}="${!prefixed_envvar}"
11 | done
12 | elif [ "$TRAVIS_BRANCH" = "master" ]; then
13 | for prefixed_envvar in ${!PROD_*}; do
14 | eval export ${prefixed_envvar#PROD_}="${!prefixed_envvar}"
15 | done
16 | else
17 | for prefixed_envvar in ${!TEST_*}; do
18 | eval export ${prefixed_envvar#TEST_}="${!prefixed_envvar}"
19 | done
20 | fi
21 | - openssl aes-256-cbc -K $encrypted_18b2305b78c9_key -iv $encrypted_18b2305b78c9_iv -in config/id_rsa.enc -out ~/.ssh/id_rsa -d
22 | - chmod 600 ~/.ssh/id_rsa
23 | - echo -e "Host $HOST\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
24 | - yarn global add now
25 | after_script:
26 | - scp -o stricthostkeychecking=no -r ./dist root@$HOST:$BUILD_DIR
27 | - cd ./docs && now -A $DOC_NOW_CONFIG -t $NOW_TOKEN && now alias -A $DOC_NOW_CONFIG -t $NOW_TOKEN
28 |
--------------------------------------------------------------------------------
/.umirc.js:
--------------------------------------------------------------------------------
1 | // https://umijs.org/config/
2 | import { resolve } from 'path'
3 | import { i18n } from './src/utils/config'
4 |
5 | export default {
6 | ignoreMomentLocale: true,
7 | targets: { ie: 9 },
8 | treeShaking: true,
9 | plugins: [
10 | [
11 | // https://umijs.org/plugin/umi-plugin-react.html
12 | 'umi-plugin-react',
13 | {
14 | dva: { immer: true },
15 | antd: true,
16 | dynamicImport: {
17 | webpackChunkName: true,
18 | loadingComponent: './components/Loader/Loader',
19 | },
20 | routes: {
21 | exclude: [
22 | /model\.(j|t)sx?$/,
23 | /service\.(j|t)sx?$/,
24 | /models\//,
25 | /components\//,
26 | /services\//,
27 | /chart\/Container\.js$/,
28 | /chart\/ECharts\/.+Component\.js$/,
29 | /chart\/ECharts\/.+ComPonent\.js$/,
30 | /chart\/ECharts\/theme\/.+\.js$/,
31 | /chart\/highCharts\/.+Component\.js$/,
32 | /chart\/highCharts\/mapdata\/.+\.js$/,
33 | /chart\/Recharts\/.+Component\.js$/,
34 | /chart\/Recharts\/Container\.js$/,
35 | ],
36 | update: routes => {
37 | if (!i18n) return routes
38 |
39 | const newRoutes = []
40 | for (const item of routes[0].routes) {
41 | newRoutes.push(item)
42 | if (item.path) {
43 | newRoutes.push(
44 | Object.assign({}, item, {
45 | path:
46 | `/:lang(${i18n.languages
47 | .map(item => item.key)
48 | .join('|')})` + item.path,
49 | })
50 | )
51 | }
52 | }
53 | routes[0].routes = newRoutes
54 |
55 | return routes
56 | },
57 | },
58 | dll: {
59 | include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch', 'antd/es'],
60 | },
61 | pwa: {
62 | manifestOptions: {
63 | srcPath: 'manifest.json'
64 | },
65 | }
66 | },
67 | ],
68 | ],
69 | // Theme for antd
70 | // https://ant.design/docs/react/customize-theme
71 | theme: './config/theme.config.js',
72 | // Webpack Configuration
73 | proxy: {
74 | '/api/v1/': {
75 | target: 'http://localhost:8090',
76 | changeOrigin: true,
77 | pathRewrite: { '^/api/v1/': '' },
78 | }
79 | },
80 | alias: {
81 | api: resolve(__dirname, './src/services/'),
82 | components: resolve(__dirname, './src/components'),
83 | config: resolve(__dirname, './src/utils/config'),
84 | models: resolve(__dirname, './src/models'),
85 | routes: resolve(__dirname, './src/routes'),
86 | services: resolve(__dirname, './src/services'),
87 | themes: resolve(__dirname, './src/themes'),
88 | utils: resolve(__dirname, './src/utils'),
89 | },
90 | extraBabelPresets: ['@lingui/babel-preset-react'],
91 | extraBabelPlugins: [
92 | [
93 | 'import',
94 | {
95 | libraryName: 'lodash',
96 | libraryDirectory: '',
97 | camel2DashComponentName: false,
98 | },
99 | 'lodash',
100 | ],
101 | ],
102 | }
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # family_tree_ui
2 | 家谱系统的前端代码,使用蚂蚁金服的AntDesign作为Web UI
3 |
--------------------------------------------------------------------------------
/assets/standard.md:
--------------------------------------------------------------------------------
1 | ### 开发环境
2 |
3 | - OS:Windows
4 | - 编辑器:Atom
5 | - cmd:Cmder
6 |
7 | ### Atom 插件
8 |
9 | - [atom-beautify](https://atom.io/packages/atom-beautify)
10 |
11 | #### Less
12 |
13 | - [x] Beautify On Save
14 |
15 | #### Javascript
16 |
17 | - [ ] Disable Beautifying Language
18 |
19 | #### Markdown
20 |
21 | - [x] Beautify On Save
22 | - [x] Default Beautifier:Remark
23 |
24 | #### HTML
25 |
26 | - [x] Beautify On Save
27 |
28 | - [linter](https://atom.io/packages/linter)
29 | - [ ] Lint on Change
30 | - [linter-eslint](https://atom.io/packages/linter-eslint)
31 | - [x] Fix errors on save
32 |
33 | ### Cmder 主题
34 |
35 | - [Panda-Theme-Cmder](https://github.com/HamidFaraji/Panda-Theme-Cmder)
36 |
--------------------------------------------------------------------------------
/config/id_rsa.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhihua407/family_tree_ui/debf3185f1d2b85e348c75cb80a4b9e3c811631a/config/id_rsa.enc
--------------------------------------------------------------------------------
/config/theme.config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const lessToJs = require('less-vars-to-js')
4 |
5 | module.exports = () => {
6 | const themePath = path.join(__dirname, '../src/themes/default.less')
7 | return lessToJs(fs.readFileSync(themePath, 'utf8'))
8 | }
9 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testURL: 'http://localhost:8000',
3 | }
4 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Antd-Admin",
3 | "start_url": ".",
4 | "display": "standalone",
5 | "background_color": "#fff",
6 | "description": "A front-end solution for enterprise applications built upon Ant Design and UmiJS",
7 | "icons": [
8 | {
9 | "src": "lu.svg",
10 | "sizes": "72x72 96x96 128x128 144x144 152x152 192x192 384x384 512x512"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/mock/_utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Query objects that specify keys and values in an array where all values are objects.
3 | * @param {array} array An array where all values are objects, like [{key:1},{key:2}].
4 | * @param {string} key The key of the object that needs to be queried.
5 | * @param {string} value The value of the object that needs to be queried.
6 | * @return {object|undefined} Return frist object when query success.
7 | */
8 | export function queryArray(array, key, value) {
9 | if (!Array.isArray(array)) {
10 | return
11 | }
12 | return array.filter(_ => _[key] === value)
13 | }
14 |
15 | export function randomNumber(min, max) {
16 | return Math.floor(Math.random() * (max - min) + min)
17 | }
18 |
19 | export function randomAvatar() {
20 | // https://uifaces.co
21 | const avatarList = [
22 | 'https://randomuser.me/api/portraits/men/32.jpg',
23 | 'https://images.pexels.com/photos/415829/pexels-photo-415829.jpeg?h=350&auto=compress&cs=tinysrgb',
24 | 'https://d3iw72m71ie81c.cloudfront.net/female-17.jpg',
25 | 'https://randomuser.me/api/portraits/men/35.jpg',
26 | 'https://pbs.twimg.com/profile_images/835224725815246848/jdMBCxHS.jpg',
27 | 'https://pbs.twimg.com/profile_images/584098247641300992/N25WgvW_.png',
28 | 'https://d3iw72m71ie81c.cloudfront.net/male-5.jpg',
29 | 'https://images.pexels.com/photos/413723/pexels-photo-413723.jpeg?h=350&auto=compress&cs=tinysrgb',
30 | 'https://randomuser.me/api/portraits/women/44.jpg',
31 | 'https://randomuser.me/api/portraits/women/68.jpg',
32 | 'https://randomuser.me/api/portraits/women/65.jpg',
33 | 'https://randomuser.me/api/portraits/men/43.jpg',
34 | 'https://tinyfac.es/data/avatars/475605E3-69C5-4D2B-8727-61B7BB8C4699-500w.jpeg',
35 | 'https://pbs.twimg.com/profile_images/943227488292962306/teiNNAiy.jpg',
36 | 'https://randomuser.me/api/portraits/men/46.jpg'
37 | ]
38 | return avatarList[randomNumber(0, avatarList.length - 1)]
39 | }
40 |
41 | export const Constant = {
42 | ApiPrefix: '/api/v1',
43 | NotFound: {
44 | message: 'Not Found',
45 | documentation_url: '',
46 | },
47 | Color: {
48 | green: '#64ea91',
49 | blue: '#8fc9fb',
50 | purple: '#d897eb',
51 | red: '#f69899',
52 | yellow: '#f8c82e',
53 | peach: '#f797d6',
54 | borderBase: '#e5e5e5',
55 | borderSplit: '#f4f4f4',
56 | grass: '#d6fbb5',
57 | sky: '#c1e0fc',
58 | },
59 | }
60 |
61 | export Mock from 'mockjs'
62 | export qs from 'qs'
63 |
--------------------------------------------------------------------------------
/mock/dashboard.js:
--------------------------------------------------------------------------------
1 | import { Mock, Constant } from './_utils'
2 |
3 | const { ApiPrefix, Color } = Constant
4 |
5 | const Dashboard = Mock.mock({
6 | 'sales|8': [
7 | {
8 | 'name|+1': 2008,
9 | 'Clothes|200-500': 1,
10 | 'Food|180-400': 1,
11 | 'Electronics|300-550': 1,
12 | },
13 | ],
14 | cpu: {
15 | 'usage|50-600': 1,
16 | space: 825,
17 | 'cpu|40-90': 1,
18 | 'data|20': [
19 | {
20 | 'cpu|20-80': 1,
21 | },
22 | ],
23 | },
24 | browser: [
25 | {
26 | name: 'Google Chrome',
27 | percent: 43.3,
28 | status: 1,
29 | },
30 | {
31 | name: 'Mozilla Firefox',
32 | percent: 33.4,
33 | status: 2,
34 | },
35 | {
36 | name: 'Apple Safari',
37 | percent: 34.6,
38 | status: 3,
39 | },
40 | {
41 | name: 'Internet Explorer',
42 | percent: 12.3,
43 | status: 4,
44 | },
45 | {
46 | name: 'Opera Mini',
47 | percent: 3.3,
48 | status: 1,
49 | },
50 | {
51 | name: 'Chromium',
52 | percent: 2.53,
53 | status: 1,
54 | },
55 | ],
56 | user: {
57 | name: 'github',
58 | sales: 3241,
59 | sold: 3556,
60 | },
61 | 'completed|12': [
62 | {
63 | 'name|+1': 2008,
64 | 'Task complete|200-1000': 1,
65 | 'Cards Complete|200-1000': 1,
66 | },
67 | ],
68 | 'comments|5': [
69 | {
70 | name: '@last',
71 | 'status|1-3': 1,
72 | content: '@sentence',
73 | avatar() {
74 | return Mock.Random.image(
75 | '48x48',
76 | Mock.Random.color(),
77 | '#757575',
78 | 'png',
79 | this.name.substr(0, 1)
80 | )
81 | },
82 | date() {
83 | return `2016-${Mock.Random.date('MM-dd')} ${Mock.Random.time(
84 | 'HH:mm:ss'
85 | )}`
86 | },
87 | },
88 | ],
89 | 'recentSales|36': [
90 | {
91 | 'id|+1': 1,
92 | name: '@last',
93 | 'status|1-4': 1,
94 | date() {
95 | return `${Mock.Random.integer(2015, 2016)}-${Mock.Random.date(
96 | 'MM-dd'
97 | )} ${Mock.Random.time('HH:mm:ss')}`
98 | },
99 | 'price|10-200.1-2': 1,
100 | },
101 | ],
102 | quote: {
103 | name: 'Joho Doe',
104 | title: 'Graphic Designer',
105 | content:
106 | "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.",
107 | avatar:
108 | 'http://img.hb.aicdn.com/bc442cf0cc6f7940dcc567e465048d1a8d634493198c4-sPx5BR_fw236',
109 | },
110 | numbers: [
111 | {
112 | icon: 'pay-circle-o',
113 | color: Color.green,
114 | title: 'Online Review',
115 | number: 2781,
116 | },
117 | {
118 | icon: 'team',
119 | color: Color.blue,
120 | title: 'New Customers',
121 | number: 3241,
122 | },
123 | {
124 | icon: 'message',
125 | color: Color.purple,
126 | title: 'Active Projects',
127 | number: 253,
128 | },
129 | {
130 | icon: 'shopping-cart',
131 | color: Color.red,
132 | title: 'Referrals',
133 | number: 4324,
134 | },
135 | ],
136 | })
137 |
138 | module.exports = {
139 | [`GET ${ApiPrefix}/dashboard`](req, res) {
140 | res.json(Dashboard)
141 | },
142 | }
143 |
--------------------------------------------------------------------------------
/mock/post.js:
--------------------------------------------------------------------------------
1 | import { Mock, Constant } from './_utils'
2 |
3 | const { ApiPrefix } = Constant
4 |
5 | let postId = 0
6 | const database = Mock.mock({
7 | 'data|100': [
8 | {
9 | id() {
10 | postId += 1
11 | return postId + 10000
12 | },
13 | 'status|1-2': 1,
14 | title: '@title',
15 | author: '@last',
16 | categories: '@word',
17 | tags: '@word',
18 | 'views|10-200': 1,
19 | 'comments|10-200': 1,
20 | visibility: () => {
21 | return Mock.mock(
22 | '@pick(["Public",' + '"Password protected", ' + '"Private"])'
23 | )
24 | },
25 | date: '@dateTime',
26 | image() {
27 | return Mock.Random.image(
28 | '100x100',
29 | Mock.Random.color(),
30 | '#757575',
31 | 'png',
32 | this.author.substr(0, 1)
33 | )
34 | },
35 | },
36 | ],
37 | }).data
38 |
39 | module.exports = {
40 | [`GET ${ApiPrefix}/posts`](req, res) {
41 | const { query } = req
42 | let { pageSize, page, ...other } = query
43 | pageSize = pageSize || 10
44 | page = page || 1
45 |
46 | let newData = database
47 | for (let key in other) {
48 | if ({}.hasOwnProperty.call(other, key)) {
49 | newData = newData.filter(item => {
50 | if ({}.hasOwnProperty.call(item, key)) {
51 | return (
52 | String(item[key])
53 | .trim()
54 | .indexOf(decodeURI(other[key]).trim()) > -1
55 | )
56 | }
57 | return true
58 | })
59 | }
60 | }
61 |
62 | res.status(200).json({
63 | data: newData.slice((page - 1) * pageSize, page * pageSize),
64 | total: newData.length,
65 | })
66 | },
67 | }
68 |
--------------------------------------------------------------------------------
/mock/route.js:
--------------------------------------------------------------------------------
1 | import { Constant } from './_utils'
2 | const { ApiPrefix } = Constant
3 |
4 | const database = [
5 | {
6 | id: '1',
7 | icon: 'dashboard',
8 | name: 'Dashboard',
9 | zh: {
10 | name: '仪表盘'
11 | },
12 | 'pt-br': {
13 | name: 'Dashboard'
14 | },
15 | route: '/dashboard',
16 | },
17 | {
18 | id: '2',
19 | breadcrumbParentId: '1',
20 | name: 'Users',
21 | zh: {
22 | name: '用户管理'
23 | },
24 | 'pt-br': {
25 | name: 'Usuário'
26 | },
27 | icon: 'user',
28 | route: '/user',
29 | },
30 | {
31 | id: '7',
32 | breadcrumbParentId: '1',
33 | name: 'Posts',
34 | zh: {
35 | name: '用户管理'
36 | },
37 | 'pt-br': {
38 | name: 'Posts'
39 | },
40 | icon: 'shopping-cart',
41 | route: '/post',
42 | },
43 | {
44 | id: '21',
45 | menuParentId: '-1',
46 | breadcrumbParentId: '2',
47 | name: 'User Detail',
48 | zh: {
49 | name: '用户详情'
50 | },
51 | 'pt-br': {
52 | name: 'Detalhes do usuário'
53 | },
54 | route: '/user/:id',
55 | },
56 | {
57 | id: '3',
58 | breadcrumbParentId: '1',
59 | name: 'Request',
60 | zh: {
61 | name: 'Request'
62 | },
63 | 'pt-br': {
64 | name: 'Requisição'
65 | },
66 | icon: 'api',
67 | route: '/request',
68 | },
69 | {
70 | id: '4',
71 | breadcrumbParentId: '1',
72 | name: 'UI Element',
73 | zh: {
74 | name: 'UI组件'
75 | },
76 | 'pt-br': {
77 | name: 'Elementos UI'
78 | },
79 | icon: 'camera-o',
80 | },
81 | {
82 | id: '45',
83 | breadcrumbParentId: '4',
84 | menuParentId: '4',
85 | name: 'Editor',
86 | zh: {
87 | name: 'Editor'
88 | },
89 | 'pt-br': {
90 | name: 'Editor'
91 | },
92 | icon: 'edit',
93 | route: '/UIElement/editor',
94 | },
95 | {
96 | id: '5',
97 | breadcrumbParentId: '1',
98 | name: 'Charts',
99 | zh: {
100 | name: 'Charts'
101 | },
102 | 'pt-br': {
103 | name: 'Graficos'
104 | },
105 | icon: 'code-o',
106 | },
107 | {
108 | id: '51',
109 | breadcrumbParentId: '5',
110 | menuParentId: '5',
111 | name: 'ECharts',
112 | zh: {
113 | name: 'ECharts'
114 | },
115 | 'pt-br': {
116 | name: 'ECharts'
117 | },
118 | icon: 'line-chart',
119 | route: '/chart/ECharts',
120 | },
121 | {
122 | id: '52',
123 | breadcrumbParentId: '5',
124 | menuParentId: '5',
125 | name: 'HighCharts',
126 | zh: {
127 | name: 'HighCharts'
128 | },
129 | 'pt-br': {
130 | name: 'HighCharts'
131 | },
132 | icon: 'bar-chart',
133 | route: '/chart/highCharts',
134 | },
135 | {
136 | id: '53',
137 | breadcrumbParentId: '5',
138 | menuParentId: '5',
139 | name: 'Rechartst',
140 | zh: {
141 | name: 'Rechartst'
142 | },
143 | 'pt-br': {
144 | name: 'Rechartst'
145 | },
146 | icon: 'area-chart',
147 | route: '/chart/Recharts',
148 | },
149 | ]
150 |
151 | module.exports = {
152 | [`GET ${ApiPrefix}/routes`](req, res) {
153 | res.status(200).json(database)
154 | },
155 | }
156 |
--------------------------------------------------------------------------------
/public/america.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
64 |
--------------------------------------------------------------------------------
/public/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhihua407/family_tree_ui/debf3185f1d2b85e348c75cb80a4b9e3c811631a/public/avatar.png
--------------------------------------------------------------------------------
/public/china.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhihua407/family_tree_ui/debf3185f1d2b85e348c75cb80a4b9e3c811631a/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/lu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhihua407/family_tree_ui/debf3185f1d2b85e348c75cb80a4b9e3c811631a/public/lu.jpg
--------------------------------------------------------------------------------
/scripts/translate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Youdao Translate, My private account is for testing purposes only.
3 | * Please go to the official account to apply for an account. Thank you for your cooperation.
4 | * APP ID:055c2d71f9a05214
5 | * Secret key:ZcpuQxQW3NkQeKVkqrXIKQYXH57g2KuN
6 | */
7 |
8 | /* eslint-disable */
9 | const md5 = require('md5')
10 | const qs = require('qs')
11 | const fs = require('fs')
12 | const path = require('path')
13 | const axios = require('axios')
14 | const jsonFormat = require('json-format')
15 | const { i18n } = require('../src/utils/config')
16 | const { languages, defaultLanguage } = i18n
17 |
18 | const locales = {}
19 |
20 | languages.forEach(item => {
21 | locales[item.key] = require(`../src/locales/${item.key}/messages.json`)
22 | })
23 |
24 | const youdao = ({ q, from, to }) =>
25 | new Promise((resolve, reject) => {
26 | {
27 | const appid = '055c2d71f9a05214'
28 | const appse = 'ZcpuQxQW3NkQeKVkqrXIKQYXH57g2KuN'
29 | const salt = Date.now()
30 |
31 | const sign = md5(appid + q + salt + appse)
32 | const query = qs.stringify({
33 | q,
34 | from,
35 | to,
36 | appKey: appid,
37 | salt,
38 | sign,
39 | })
40 |
41 | axios.get(`http://openapi.youdao.com/api?${query}`).then(({ data }) => {
42 | if (data.query && data.translation[0]) {
43 | resolve(data.translation[0])
44 | } else {
45 | resolve(q)
46 | }
47 | })
48 | }
49 | })
50 |
51 | const transform = async ({ from, to, locales, outputPath }) => {
52 | for (const key in locales[from]) {
53 | if (locales[to][key]) {
54 | console.log(`add to skip: ${key}`)
55 | } else {
56 | let res = key
57 | let way = 'youdao'
58 | if (key.indexOf('/') !== 0) {
59 | const reg = '{([^{}]*)}'
60 | const tasks = key
61 | .match(new RegExp(`${reg}|((?<=(${reg}|^)).*?(?=(${reg}|$)))`, 'g'))
62 | .map(item => {
63 | if (new RegExp(reg).test(item)) {
64 | return Promise.resolve(item)
65 | }
66 | return youdao({
67 | q: item,
68 | from,
69 | to,
70 | })
71 | })
72 |
73 | res = (await Promise.all(tasks)).join('')
74 | } else {
75 | res = `/${to + key}`
76 | way = 'link'
77 | }
78 | if (res !== key) {
79 | locales[to][key] = res
80 | console.log(`${way}: ${from} -> ${to}: ${key} -> ${res}`)
81 | } else {
82 | console.log(`same: ${from} -> ${to}: ${key}`)
83 | }
84 | }
85 | }
86 | await fs.writeFileSync(
87 | path.resolve(__dirname, outputPath),
88 | jsonFormat(locales[to], {
89 | type: 'space',
90 | size: 2,
91 | })
92 | )
93 | }
94 | ;(async () => {
95 | const tasks = languages
96 | .map(item => ({
97 | from: defaultLanguage,
98 | to: item.key,
99 | }))
100 | .filter(item => item.from !== item.to)
101 |
102 | for (const item of tasks) {
103 | console.log(`start: ${item.from} -> ${item.to}`)
104 | await transform({
105 | from: item.from,
106 | to: item.to,
107 | locales,
108 | outputPath: `../src/locales/${item.to}/messages.json`,
109 | })
110 | console.log(`completed: ${item.from} -> ${item.to}`)
111 | }
112 |
113 | console.log('All translations have been completed.')
114 | })()
115 |
--------------------------------------------------------------------------------
/src/components/DropOption/DropOption.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Dropdown, Button, Icon, Menu } from 'antd'
4 |
5 | const DropOption = ({
6 | onMenuClick,
7 | menuOptions = [],
8 | buttonStyle,
9 | dropdownProps,
10 | }) => {
11 | const menu = menuOptions.map(item => (
12 |
{item.name}
13 | ))
14 | return (
15 | {menu}}
17 | {...dropdownProps}
18 | >
19 |
23 |
24 | )
25 | }
26 |
27 | DropOption.propTypes = {
28 | onMenuClick: PropTypes.func,
29 | menuOptions: PropTypes.array.isRequired,
30 | buttonStyle: PropTypes.object,
31 | dropdownProps: PropTypes.object,
32 | }
33 |
34 | export default DropOption
35 |
--------------------------------------------------------------------------------
/src/components/DropOption/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DropOption",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./DropOption.js"
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/Editor/Editor.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Editor } from 'react-draft-wysiwyg'
3 | import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
4 | import styles from './Editor.less'
5 |
6 | const DraftEditor = props => {
7 | return (
8 |
14 | )
15 | }
16 |
17 | export default DraftEditor
18 |
--------------------------------------------------------------------------------
/src/components/Editor/Editor.less:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | height: 500px;
3 |
4 | :global {
5 | .rdw-dropdownoption-default {
6 | padding: 6px;
7 | }
8 |
9 | .rdw-dropdown-optionwrapper {
10 | box-sizing: content-box;
11 | width: 100%;
12 | border-radius: 0 0 2px 2px;
13 | &:hover {
14 | box-shadow: none;
15 | }
16 | }
17 |
18 | .rdw-inline-wrapper {
19 | flex-wrap: wrap;
20 | margin-bottom: 0;
21 |
22 | .rdw-option-wrapper {
23 | margin-bottom: 6px;
24 | }
25 | }
26 |
27 | .rdw-option-active {
28 | box-shadow: 1px 1px 0 #e8e8e8 inset;
29 | }
30 |
31 | .rdw-colorpicker-option {
32 | box-shadow: none;
33 | }
34 |
35 | .rdw-colorpicker-modal,
36 | .rdw-embedded-modal,
37 | .rdw-emoji-modal,
38 | .rdw-image-modal,
39 | .rdw-link-modal {
40 | box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
41 | }
42 |
43 | .rdw-colorpicker-modal,
44 | .rdw-embedded-modal,
45 | .rdw-link-modal {
46 | height: auto;
47 | }
48 |
49 | .rdw-emoji-modal {
50 | width: 214px;
51 | }
52 |
53 | .rdw-colorpicker-modal {
54 | width: auto;
55 | }
56 |
57 | .rdw-embedded-modal-btn,
58 | .rdw-image-modal-btn,
59 | .rdw-link-modal-btn {
60 | height: 32px;
61 | margin-top: 12px;
62 | }
63 |
64 | .rdw-embedded-modal-input,
65 | .rdw-embedded-modal-size-input,
66 | .rdw-link-modal-input {
67 | padding: 2px 6px;
68 | height: 32px;
69 | }
70 |
71 | .rdw-dropdown-selectedtext {
72 | color: #000;
73 | }
74 |
75 | .rdw-dropdown-wrapper,
76 | .rdw-option-wrapper {
77 | min-width: 36px;
78 | transition: all 0.2s ease;
79 | height: 30px;
80 |
81 | &:active {
82 | box-shadow: 1px 1px 0 #e8e8e8 inset;
83 | }
84 |
85 | &:hover {
86 | box-shadow: 1px 1px 0 #e8e8e8;
87 | }
88 | }
89 |
90 | .rdw-dropdown-wrapper {
91 | min-width: 60px;
92 | }
93 |
94 | .rdw-editor-main {
95 | box-sizing: border-box;
96 | }
97 | }
98 |
99 | .editor {
100 | border: 1px solid #f1f1f1;
101 | padding: 5px;
102 | border-radius: 2px;
103 | height: auto;
104 | min-height: 200px;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/components/Editor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Editor",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./Editor.js"
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/FilterItem/FilterItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import styles from './FilterItem.less'
4 |
5 | const FilterItem = ({ label = '', children }) => {
6 | const labelArray = label.split('')
7 | return (
8 |
9 | {labelArray.length > 0 ? (
10 |
11 | {labelArray.map((item, index) => (
12 |
13 | {item}
14 |
15 | ))}
16 |
17 | ) : (
18 | ''
19 | )}
20 |
{children}
21 |
22 | )
23 | }
24 |
25 | FilterItem.propTypes = {
26 | label: PropTypes.string,
27 | children: PropTypes.element.isRequired,
28 | }
29 |
30 | export default FilterItem
31 |
--------------------------------------------------------------------------------
/src/components/FilterItem/FilterItem.less:
--------------------------------------------------------------------------------
1 | .filterItem {
2 | display: flex;
3 | justify-content: space-between;
4 |
5 | .labelWrap {
6 | width: 64px;
7 | line-height: 28px;
8 | margin-right: 12px;
9 | justify-content: space-between;
10 | display: flex;
11 | overflow: hidden;
12 | }
13 |
14 | .item {
15 | flex: 1;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/FilterItem/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FilterItem",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./FilterItem.js"
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/Layout/Bread.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent, Fragment } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Breadcrumb, Icon } from 'antd'
4 | import Link from 'umi/navlink'
5 | import withRouter from 'umi/withRouter'
6 | import { withI18n } from '@lingui/react'
7 | import { pathMatchRegexp, queryAncestors, addLangPrefix } from 'utils'
8 | import styles from './Bread.less'
9 |
10 | @withI18n()
11 | @withRouter
12 | class Bread extends PureComponent {
13 | generateBreadcrumbs = paths => {
14 | return paths.map((item, key) => {
15 | const content = item && (
16 |
17 | {item.icon ? (
18 |
19 | ) : null}
20 | {item.name}
21 |
22 | )
23 |
24 | return (
25 | item && (
26 |
27 | {paths.length - 1 !== key ? (
28 | {content}
29 | ) : (
30 | content
31 | )}
32 |
33 | )
34 | )
35 | })
36 | }
37 | render() {
38 | const { routeList, location, i18n } = this.props
39 |
40 | // Find a route that matches the pathname.
41 | const currentRoute = routeList.find(
42 | _ => _.route && pathMatchRegexp(_.route, location.pathname)
43 | )
44 |
45 | // Find the breadcrumb navigation of the current route match and all its ancestors.
46 | const paths = currentRoute
47 | ? queryAncestors(routeList, currentRoute, 'breadcrumbParentId').reverse()
48 | : [
49 | routeList[0],
50 | {
51 | id: 404,
52 | name: i18n.t`Not Found`,
53 | },
54 | ]
55 |
56 | return (
57 |
58 | {this.generateBreadcrumbs(paths)}
59 |
60 | )
61 | }
62 | }
63 |
64 | Bread.propTypes = {
65 | routeList: PropTypes.array,
66 | }
67 |
68 | export default Bread
69 |
--------------------------------------------------------------------------------
/src/components/Layout/Bread.less:
--------------------------------------------------------------------------------
1 | .bread {
2 | margin-bottom: 24px;
3 |
4 | :global {
5 | .ant-breadcrumb {
6 | display: flex;
7 | align-items: center;
8 | }
9 | }
10 | }
11 |
12 | @media (max-width: 767px) {
13 | .bread {
14 | margin-bottom: 12px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Layout/Sider.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Icon, Switch, Layout } from 'antd'
4 | import { withI18n, Trans } from '@lingui/react'
5 | import ScrollBar from '../ScrollBar'
6 | import { config } from 'utils'
7 | import SiderMenu from './Menu'
8 | import styles from './Sider.less'
9 |
10 | @withI18n()
11 | class Sider extends PureComponent {
12 | render() {
13 | const {
14 | i18n,
15 | menus,
16 | theme,
17 | isMobile,
18 | collapsed,
19 | onThemeChange,
20 | onCollapseChange,
21 | } = this.props
22 |
23 | return (
24 |
34 |
35 |
36 |

37 | {collapsed ? null :
{config.siteName}
}
38 |
39 |
40 |
41 |
42 |
48 |
55 |
56 |
57 | {collapsed ? null : (
58 |
59 |
60 |
61 | Switch Theme
62 |
63 |
72 |
73 | )}
74 |
75 | )
76 | }
77 | }
78 |
79 | Sider.propTypes = {
80 | menus: PropTypes.array,
81 | theme: PropTypes.string,
82 | isMobile: PropTypes.bool,
83 | collapsed: PropTypes.bool,
84 | onThemeChange: PropTypes.func,
85 | onCollapseChange: PropTypes.func,
86 | }
87 |
88 | export default Sider
89 |
--------------------------------------------------------------------------------
/src/components/Layout/Sider.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars.less';
2 |
3 | .sider {
4 | box-shadow: fade(@primary-color, 10%) 0 0 28px 0;
5 | z-index: 10;
6 | :global {
7 | .ant-layout-sider-children {
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: space-between;
11 | }
12 | }
13 | }
14 |
15 | .brand {
16 | z-index: 1;
17 | height: 72px;
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | padding: 0 24px;
22 | box-shadow: 0 1px 9px -3px rgba(0, 0, 0, 0.2);
23 | .logo {
24 | display: flex;
25 | align-items: center;
26 | justify-content: center;
27 |
28 | img {
29 | width: 36px;
30 | margin-right: 8px;
31 | }
32 |
33 | h1 {
34 | vertical-align: text-bottom;
35 | font-size: 16px;
36 | text-transform: uppercase;
37 | display: inline-block;
38 | font-weight: 700;
39 | color: @primary-color;
40 | white-space: nowrap;
41 | margin-bottom: 0;
42 | .text-gradient();
43 |
44 | :local {
45 | animation: fadeRightIn 300ms @ease-in-out;
46 | animation-fill-mode: both;
47 | }
48 | }
49 | }
50 | }
51 |
52 | .menuContainer {
53 | height: ~'calc(100vh - 120px)';
54 | overflow-x: hidden;
55 | flex: 1;
56 | padding: 24px 0;
57 |
58 | &::-webkit-scrollbar-thumb {
59 | background-color: transparent;
60 | }
61 |
62 | &:hover {
63 | &::-webkit-scrollbar-thumb {
64 | background-color: rgba(0, 0, 0, 0.2);
65 | }
66 | }
67 |
68 | :global {
69 | .ant-menu-inline {
70 | border-right: none;
71 | }
72 | }
73 | }
74 |
75 | .switchTheme {
76 | width: 100%;
77 | height: 48px;
78 | display: flex;
79 | justify-content: space-between;
80 | align-items: center;
81 | padding: 0 16px;
82 | overflow: hidden;
83 | transition: all 0.3s;
84 |
85 | span {
86 | white-space: nowrap;
87 | overflow: hidden;
88 | font-size: 12px;
89 | }
90 |
91 | :global {
92 | .anticon {
93 | min-width: 14px;
94 | margin-right: 4px;
95 | font-size: 14px;
96 | }
97 | }
98 | }
99 |
100 | @keyframes fadeLeftIn {
101 | 0% {
102 | transform: translateX(5px);
103 | opacity: 0;
104 | }
105 |
106 | 100% {
107 | transform: translateX(0);
108 | opacity: 1;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/components/Layout/index.js:
--------------------------------------------------------------------------------
1 | import Header from './Header'
2 | import Menu from './Menu'
3 | import Bread from './Bread'
4 | import Sider from './Sider'
5 |
6 | export { Header, Menu, Bread, Sider }
7 |
--------------------------------------------------------------------------------
/src/components/Loader/Loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classNames from 'classnames'
4 | import styles from './Loader.less'
5 |
6 | const Loader = ({ spinning = true, fullScreen }) => {
7 | return (
8 |
19 | )
20 | }
21 |
22 | Loader.propTypes = {
23 | spinning: PropTypes.bool,
24 | fullScreen: PropTypes.bool,
25 | }
26 |
27 | export default Loader
28 |
--------------------------------------------------------------------------------
/src/components/Loader/Loader.less:
--------------------------------------------------------------------------------
1 | .loader {
2 | background-color: #fff;
3 | width: 100%;
4 | position: absolute;
5 | top: 0;
6 | bottom: 0;
7 | left: 0;
8 | z-index: 100000;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | opacity: 1;
13 | text-align: center;
14 |
15 | &.fullScreen {
16 | position: fixed;
17 | }
18 |
19 | .warpper {
20 | width: 100px;
21 | height: 100px;
22 | display: inline-flex;
23 | flex-direction: column;
24 | justify-content: space-around;
25 | }
26 |
27 | .inner {
28 | width: 40px;
29 | height: 40px;
30 | margin: 0 auto;
31 | text-indent: -12345px;
32 | border-top: 1px solid rgba(0, 0, 0, 0.08);
33 | border-right: 1px solid rgba(0, 0, 0, 0.08);
34 | border-bottom: 1px solid rgba(0, 0, 0, 0.08);
35 | border-left: 1px solid rgba(0, 0, 0, 0.7);
36 | border-radius: 50%;
37 | z-index: 100001;
38 |
39 | :local {
40 | animation: spinner 600ms infinite linear;
41 | }
42 | }
43 |
44 | .text {
45 | width: 100px;
46 | height: 20px;
47 | text-align: center;
48 | font-size: 12px;
49 | letter-spacing: 4px;
50 | color: #000;
51 | }
52 |
53 | &.hidden {
54 | z-index: -1;
55 | opacity: 0;
56 | transition: opacity 1s ease 0.5s, z-index 0.1s ease 1.5s;
57 | }
58 | }
59 | @keyframes spinner {
60 | 0% {
61 | transform: rotate(0deg);
62 | }
63 |
64 | 100% {
65 | transform: rotate(360deg);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/Loader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Loader",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./Loader.js"
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/Page/Page.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 | import Loader from '../Loader'
5 | import styles from './Page.less'
6 |
7 | export default class Page extends Component {
8 | render() {
9 | const { className, children, loading = false, inner = false } = this.props
10 | const loadingStyle = {
11 | height: 'calc(100vh - 184px)',
12 | overflow: 'hidden',
13 | }
14 | return (
15 |
21 | {loading ? : ''}
22 | {children}
23 |
24 | )
25 | }
26 | }
27 |
28 | Page.propTypes = {
29 | className: PropTypes.string,
30 | children: PropTypes.node,
31 | loading: PropTypes.bool,
32 | inner: PropTypes.bool,
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/Page/Page.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars.less';
2 |
3 | .contentInner {
4 | background: #fff;
5 | padding: 24px;
6 | box-shadow: @shadow-1;
7 | min-height: ~'calc(100vh - 230px)';
8 | position: relative;
9 | }
10 |
11 | @media (max-width: 767px) {
12 | .contentInner {
13 | padding: 12px;
14 | min-height: ~'calc(100vh - 160px)';
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Page/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Page",
3 | "version": "0.0.0",
4 | "private": true,
5 | "main": "./Page.js"
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/ScrollBar/index.js:
--------------------------------------------------------------------------------
1 | import ScrollBar from 'react-perfect-scrollbar'
2 | import 'react-perfect-scrollbar/dist/css/styles.css'
3 | import './index.less'
4 |
5 | export default ScrollBar
6 |
--------------------------------------------------------------------------------
/src/components/ScrollBar/index.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .ps--active-x > .ps__rail-x,
3 | .ps--active-y > .ps__rail-y {
4 | background-color: transparent;
5 | }
6 |
7 | .ps__rail-x:hover > .ps__thumb-x,
8 | .ps__rail-x:focus > .ps__thumb-x {
9 | height: 8px;
10 | }
11 |
12 | .ps__rail-y:hover > .ps__thumb-y,
13 | .ps__rail-y:focus > .ps__thumb-y {
14 | width: 8px;
15 | }
16 |
17 | .ps__rail-y,
18 | .ps__rail-x {
19 | z-index: 9;
20 | }
21 |
22 | .ps__thumb-y {
23 | width: 4px;
24 | right: 4px;
25 | }
26 |
27 | .ps__thumb-x {
28 | height: 4px;
29 | bottom: 4px;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Editor from './Editor'
2 | import FilterItem from './FilterItem'
3 | import DropOption from './DropOption'
4 | import Loader from './Loader'
5 | import ScrollBar from './ScrollBar'
6 | import * as MyLayout from './Layout/index.js'
7 | import Page from './Page'
8 |
9 | export { MyLayout, Editor, FilterItem, DropOption, Loader, Page, ScrollBar }
10 |
--------------------------------------------------------------------------------
/src/e2e/login.e2e.js:
--------------------------------------------------------------------------------
1 | import puppeteer from 'puppeteer'
2 |
3 | describe('Login', () => {
4 | let browser
5 | let page
6 |
7 | beforeAll(async () => {
8 | browser = await puppeteer.launch({ args: ['--no-sandbox'] })
9 | })
10 |
11 | beforeEach(async () => {
12 | page = await browser.newPage()
13 | await page.goto('http://localhost:8000/en/login', {
14 | waitUntil: 'networkidle2',
15 | })
16 | })
17 |
18 | afterEach(() => page.close())
19 |
20 | it('should login with failure', async () => {
21 | await page.waitFor(selector => !!document.querySelector('#username'), {
22 | timeout: 3000,
23 | })
24 | await page.type('#username', 'wrong_user')
25 | await page.type('#password', 'wrong_password')
26 | await page.click('button[type="button"]')
27 | await page.waitForSelector('.anticon-close-circle') // should display error
28 | })
29 |
30 | it('should login successfully', async () => {
31 | await page.waitForSelector('#username', { timeout: 3000 })
32 | await page.type('#username', 'admin')
33 | await page.type('#password', 'admin')
34 | await page.click('button[type="button"]')
35 | await page.waitForSelector('.ant-layout-footer')
36 | const text = await page.evaluate(() => document.body.innerHTML)
37 | expect(text).toContain('Ant Design Admin')
38 | })
39 |
40 | afterAll(() => browser.close())
41 | })
42 |
--------------------------------------------------------------------------------
/src/layouts/BaseLayout.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent, Fragment } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Helmet } from 'react-helmet'
5 | import { Loader } from 'components'
6 | import { queryLayout } from 'utils'
7 | import NProgress from 'nprogress'
8 | import config from 'utils/config'
9 | import withRouter from 'umi/withRouter'
10 |
11 | import PublicLayout from './PublicLayout'
12 | import PrimaryLayout from './PrimaryLayout'
13 | import './BaseLayout.less'
14 |
15 | const LayoutMap = {
16 | primary: PrimaryLayout,
17 | public: PublicLayout,
18 | }
19 |
20 | @withRouter
21 | @connect(({ loading }) => ({ loading }))
22 | class BaseLayout extends PureComponent {
23 | previousPath = ''
24 |
25 | render() {
26 | const { loading, children, location } = this.props
27 | const Container = LayoutMap[queryLayout(config.layouts, location.pathname)]
28 | const currentPath = location.pathname + location.search
29 | if (currentPath !== this.previousPath) {
30 | NProgress.start()
31 | }
32 |
33 | if (!loading.global) {
34 | NProgress.done()
35 | this.previousPath = currentPath
36 | }
37 |
38 | return (
39 |
40 |
41 | {config.siteName}
42 |
43 |
44 | {children}
45 |
46 | )
47 | }
48 | }
49 |
50 | BaseLayout.propTypes = {
51 | loading: PropTypes.object,
52 | }
53 |
54 | export default BaseLayout
55 |
--------------------------------------------------------------------------------
/src/layouts/BaseLayout.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars.less';
2 | @import '~themes/index.less';
3 |
4 | :global {
5 | #nprogress {
6 | pointer-events: none;
7 |
8 | .bar {
9 | background: @primary-color;
10 | position: fixed;
11 | z-index: 2048;
12 | top: 0;
13 | left: 0;
14 | right: 0;
15 | width: 100%;
16 | height: 2px;
17 | }
18 |
19 | .peg {
20 | display: block;
21 | position: absolute;
22 | right: 0;
23 | width: 100px;
24 | height: 100%;
25 | box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color;
26 | opacity: 1;
27 | transform: rotate(3deg) translate(0, -4px);
28 | }
29 |
30 | .spinner {
31 | display: block;
32 | position: fixed;
33 | z-index: 1031;
34 | top: 15px;
35 | right: 15px;
36 | }
37 |
38 | .spinner-icon {
39 | width: 18px;
40 | height: 18px;
41 | box-sizing: border-box;
42 | border: solid 2px transparent;
43 | border-top-color: @primary-color;
44 | border-left-color: @primary-color;
45 | border-radius: 50%;
46 |
47 | :local {
48 | animation: nprogress-spinner 400ms linear infinite;
49 | }
50 | }
51 | }
52 |
53 | .nprogress-custom-parent {
54 | overflow: hidden;
55 | position: relative;
56 |
57 | #nprogress {
58 | .bar,
59 | .spinner {
60 | position: absolute;
61 | }
62 | }
63 | }
64 | }
65 |
66 | @keyframes nprogress-spinner {
67 | 0% {
68 | transform: rotate(0deg);
69 | }
70 |
71 | 100% {
72 | transform: rotate(360deg);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/layouts/PrimaryLayout.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars.less';
2 |
3 | .backTop {
4 | right: 50px;
5 |
6 | :global {
7 | .ant-back-top-content {
8 | background: @primary-color;
9 | opacity: 0.3;
10 | transition: all 0.3s;
11 | box-shadow: 0 0 15px 1px rgba(69, 65, 78, 0.1);
12 |
13 | &:hover {
14 | opacity: 1;
15 | }
16 | }
17 | }
18 | }
19 |
20 | .content {
21 | padding: 24px;
22 | min-height: ~'calc(100% - 72px)';
23 | // overflow-y: scroll;
24 | }
25 |
26 | .container {
27 | height: 100vh;
28 | flex: 1;
29 | width: ~'calc(100% - 256px)';
30 | overflow-y: scroll;
31 | overflow-x: hidden;
32 | }
33 |
34 | .footer {
35 | background: #fff;
36 | margin-top: 0;
37 | margin-bottom: 0;
38 | padding-top: 24px;
39 | padding-bottom: 24px;
40 | min-height: 72px;
41 | }
42 |
43 | @media (max-width: 767px) {
44 | .content {
45 | padding: 12px;
46 | }
47 |
48 | .backTop {
49 | right: 20px;
50 | bottom: 20px;
51 | }
52 |
53 | .container {
54 | height: 100vh;
55 | flex: 1;
56 | width: 100%;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/layouts/PublicLayout.js:
--------------------------------------------------------------------------------
1 | export default ({ children }) => {
2 | return children
3 | }
4 |
--------------------------------------------------------------------------------
/src/layouts/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import withRouter from 'umi/withRouter'
3 | import { I18nProvider } from '@lingui/react'
4 | import { ConfigProvider } from 'antd'
5 | import { langFromPath, defaultLanguage } from 'utils'
6 | import zh_CN from 'antd/lib/locale-provider/zh_CN'
7 | import BaseLayout from './BaseLayout'
8 | const languages = {
9 | zh: zh_CN,
10 | }
11 | const locale = zh_CN
12 | @withRouter
13 | class Layout extends Component {
14 | state = {
15 | catalogs: {},
16 | }
17 |
18 | language = defaultLanguage
19 |
20 | componentDidMount() {
21 | const language = langFromPath(this.props.location.pathname)
22 | this.language = language
23 | this.loadCatalog(language)
24 | }
25 |
26 | shouldComponentUpdate(nextProps, nextState) {
27 | const language = langFromPath(nextProps.location.pathname)
28 | const preLanguage = this.language
29 | const { catalogs } = nextState
30 |
31 | if (preLanguage !== language && !catalogs[language]) {
32 | this.loadCatalog(language)
33 | this.language = language
34 | return false
35 | }
36 | this.language = language
37 |
38 | return true
39 | }
40 |
41 | loadCatalog = async language => {
42 | const catalog = await import(
43 | /* webpackMode: "lazy", webpackChunkName: "i18n-[index]" */
44 | `@lingui/loader!../locales/zh/messages.json`
45 | )
46 |
47 | this.setState(state => ({
48 | catalogs: {
49 | ...state.catalogs,
50 | [language]: catalog,
51 | },
52 | }))
53 | }
54 |
55 | render() {
56 | const { location, children } = this.props
57 | const { catalogs } = this.state
58 |
59 | let language = langFromPath(location.pathname)
60 | // If the language pack is not loaded or is loading, use the default language
61 | if (!catalogs[language]) language = defaultLanguage
62 |
63 | return (
64 |
65 |
66 | {children}
67 |
68 |
69 | )
70 | }
71 | }
72 |
73 | export default Layout
74 |
--------------------------------------------------------------------------------
/src/locales/zh/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "/dashboard": "/zh/dashboard",
3 | "Add Param": "添加参数",
4 | "Address": "地址",
5 | "Age": "年龄",
6 | "Are you sure delete this record?": "您确定要删除这条记录吗?",
7 | "Author": "作者",
8 | "Avatar": "头像",
9 | "Categories": "类别",
10 | "Clear notifications": "清空消息",
11 | "Comments": "评论数",
12 | "Create": "创建",
13 | "Create User": "创建用户",
14 | "CreateTime": "创建时间",
15 | "Dark": "暗",
16 | "Delete": "删除",
17 | "Email": "电子邮件",
18 | "Female": "女",
19 | "Gender": "性别",
20 | "Hi,": "你好,",
21 | "Image": "图像",
22 | "Light": "明",
23 | "Male": "男性",
24 | "Name": "名字",
25 | "NickName": "昵称",
26 | "Not Found": "未找到",
27 | "Operation": "操作",
28 | "Params": "参数",
29 | "Password": "密码",
30 | "Phone": "电话",
31 | "Pick an address": "选择地址",
32 | "Please pick an address": "选择地址",
33 | "Publised": "已发布",
34 | "Publish Date": "发布日期",
35 | "Reset": "重置",
36 | "Search": "搜索",
37 | "Search Name": "搜索名字",
38 | "Send": "发送",
39 | "Sign in": "登录",
40 | "Sign out": "退出登录",
41 | "Switch Theme": "切换主题",
42 | "Tags": "标签",
43 | "The input is not valid E-mail!": "输入的电子邮件无效!",
44 | "The input is not valid phone!": "输入无效的手机!",
45 | "Title": "标题",
46 | "Total {total} Items": "总共 {total} 条记录",
47 | "Unpublished": "未发布",
48 | "Update": "修改",
49 | "Update User": "修改用户",
50 | "Username": "用户名",
51 | "Views": "浏览数",
52 | "Visibility": "可见性",
53 | "You have viewed all notifications.": "您已查看所有通知"
54 | }
55 |
--------------------------------------------------------------------------------
/src/pages/.umi/DvaContainer.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 |
3 | class DvaContainer extends Component {
4 | render() {
5 | window.g_app.router(() => this.props.children);
6 | return window.g_app.start()();
7 | }
8 | }
9 |
10 | export default DvaContainer;
11 |
--------------------------------------------------------------------------------
/src/pages/.umi/initDva.js:
--------------------------------------------------------------------------------
1 | import dva from 'dva';
2 | import createLoading from 'dva-loading';
3 |
4 | const runtimeDva = window.g_plugins.mergeConfig('dva');
5 | let app = dva({
6 | history: window.g_history,
7 |
8 | ...(runtimeDva.config || {}),
9 | });
10 |
11 | window.g_app = app;
12 | app.use(createLoading());
13 | (runtimeDva.plugins || []).forEach(plugin => {
14 | app.use(plugin);
15 | });
16 | app.use(require('../../plugins/onError.js').default);
17 | app.use(require('D:/antd-admin/antd_family_tree/node_modules/_dva-immer@0.2.4@dva-immer/lib/index.js').default());
18 | app.model({ namespace: 'app', ...(require('D:/antd-admin/antd_family_tree/src/models/app.js').default) });
19 |
--------------------------------------------------------------------------------
/src/pages/.umi/initHistory.js:
--------------------------------------------------------------------------------
1 | // create history
2 | window.g_history = require('umi/_createHistory').default({
3 | basename: window.routerBase,
4 | });
5 |
--------------------------------------------------------------------------------
/src/pages/.umi/polyfills.js:
--------------------------------------------------------------------------------
1 | import '@babel/polyfill';
2 |
3 | // Include this seperatly since it's not included in core-js
4 | // ref: https://github.com/zloirock/core-js/issues/117
5 | import '../../../node_modules/_url-polyfill@1.1.3@url-polyfill/url-polyfill.js';
6 |
--------------------------------------------------------------------------------
/src/pages/.umi/umi.js:
--------------------------------------------------------------------------------
1 | import './polyfills';
2 | import '@tmp/initHistory';
3 |
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 |
7 |
8 | // runtime plugins
9 | window.g_plugins = require('umi/_runtimePlugin');
10 | window.g_plugins.init({
11 | validKeys: ['patchRoutes','render','rootContainer','modifyRouteProps','onRouteChange','dva',],
12 | });
13 | window.g_plugins.use(require('../../../node_modules/_umi-plugin-dva@1.5.3@umi-plugin-dva/lib/runtime'));
14 |
15 | require('@tmp/initDva');
16 |
17 | // render
18 | let oldRender = () => {
19 | const rootContainer = window.g_plugins.apply('rootContainer', {
20 | initialValue: React.createElement(require('./router').default),
21 | });
22 | ReactDOM.render(
23 | rootContainer,
24 | document.getElementById('root'),
25 | );
26 | };
27 | const render = window.g_plugins.compose('render', { initialValue: oldRender });
28 |
29 | const moduleBeforeRendererPromises = [];
30 |
31 | Promise.all(moduleBeforeRendererPromises).then(() => {
32 | render();
33 | }).catch((err) => {
34 | window.console && window.console.error(err);
35 | });
36 |
37 |
38 |
39 | // hot module replacement
40 | if (module.hot) {
41 | module.hot.accept('./router', () => {
42 | oldRender();
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Icon } from 'antd'
3 | import { Page } from 'components'
4 | import styles from './404.less'
5 |
6 | const Error = () => (
7 |
8 |
9 |
10 |
404 找不到页面
11 |
12 |
13 | )
14 |
15 | export default Error
16 |
--------------------------------------------------------------------------------
/src/pages/404.less:
--------------------------------------------------------------------------------
1 | .error {
2 | color: black;
3 | text-align: center;
4 | position: absolute;
5 | top: 30%;
6 | margin-top: -50px;
7 | left: 50%;
8 | margin-left: -100px;
9 | width: 200px;
10 |
11 | :global .anticon {
12 | font-size: 48px;
13 | margin-bottom: 16px;
14 | }
15 |
16 | h1 {
17 | font-family: cursive;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/pages/UIElement/editor/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Editor } from 'components'
3 | import { convertToRaw } from 'draft-js'
4 | import { Row, Col, Card } from 'antd'
5 | import draftToHtml from 'draftjs-to-html'
6 | import draftToMarkdown from 'draftjs-to-markdown'
7 |
8 | export default class EditorPage extends React.Component {
9 | constructor(props) {
10 | super(props)
11 | this.state = {
12 | editorContent: null,
13 | }
14 | }
15 |
16 | onEditorStateChange = editorContent => {
17 | this.setState({
18 | editorContent,
19 | })
20 | }
21 |
22 | render() {
23 | const { editorContent } = this.state
24 | const colProps = {
25 | lg: 12,
26 | md: 24,
27 | style: {
28 | marginBottom: 32,
29 | },
30 | }
31 | const textareaStyle = {
32 | minHeight: 496,
33 | width: '100%',
34 | background: '#f7f7f7',
35 | borderColor: '#F1F1F1',
36 | padding: '16px 8px',
37 | }
38 | return (
39 |
40 |
41 |
42 |
43 |
53 |
54 |
55 |
56 |
57 |
68 |
69 |
70 |
71 |
72 |
83 |
84 |
85 |
86 |
87 |
98 |
99 |
100 |
101 |
102 | )
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/pages/auth.js:
--------------------------------------------------------------------------------
1 | import store from 'store'
2 | export const isAllowed = rights => {
3 | const permission = store.get('permission')
4 | //console.log(permission);
5 | if ('admin' == permission) {
6 | return true
7 | }
8 | return permission.indexOf(rights) > -1
9 | }
10 |
--------------------------------------------------------------------------------
/src/pages/chart/Container.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { ResponsiveContainer } from 'recharts'
4 | import styles from './Container.less'
5 |
6 | const Container = ({
7 | children,
8 | ratio = 5 / 2,
9 | minHeight = 250,
10 | maxHeight = 350,
11 | }) => (
12 |
13 |
14 |
15 | {children}
16 |
17 |
18 | )
19 |
20 | Container.propTypes = {
21 | children: PropTypes.element.isRequired,
22 | ratio: PropTypes.number,
23 | minHeight: PropTypes.number,
24 | maxHeight: PropTypes.number,
25 | }
26 |
27 | export default Container
28 |
--------------------------------------------------------------------------------
/src/pages/chart/Container.less:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | position: relative;
4 | display: inline-block;
5 |
6 | :global {
7 | .recharts-responsive-container {
8 | width: e('calc(100% + 56px)') !important;
9 | margin-left: -32px;
10 | }
11 | }
12 | }
13 |
14 | .content {
15 | position: absolute;
16 | left: 0;
17 | right: 0;
18 | top: 0;
19 | bottom: 0;
20 | }
21 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/ChartShowLoadingComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 |
4 | class ChartShowLoadingComponent extends React.Component {
5 | constructor() {
6 | super()
7 | this._t = null
8 | this.onChartReady = this.onChartReady.bind(this)
9 | }
10 | componentWillUnmount() {
11 | clearTimeout(this._t)
12 | }
13 |
14 | onChartReady(chart) {
15 | this._t = setTimeout(() => {
16 | chart.hideLoading()
17 | }, 3000)
18 | }
19 |
20 | render() {
21 | const getOtion = () => {
22 | const option = {
23 | title: {
24 | text: '基础雷达图',
25 | },
26 | tooltip: {},
27 | legend: {
28 | data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)'],
29 | },
30 | radar: {
31 | indicator: [
32 | { name: '销售(sales)', max: 6500 },
33 | { name: '管理(Administration)', max: 16000 },
34 | { name: '信息技术(Information Techology)', max: 30000 },
35 | { name: '客服(Customer Support)', max: 38000 },
36 | { name: '研发(Development)', max: 52000 },
37 | { name: '市场(Marketing)', max: 25000 },
38 | ],
39 | },
40 | series: [
41 | {
42 | name: '预算 vs 开销(Budget vs spending)',
43 | type: 'radar',
44 | data: [
45 | {
46 | value: [4300, 10000, 28000, 35000, 50000, 19000],
47 | name: '预算分配(Allocated Budget)',
48 | },
49 | {
50 | value: [5000, 14000, 28000, 31000, 42000, 21000],
51 | name: '实际开销(Actual Spending)',
52 | },
53 | ],
54 | },
55 | ],
56 | }
57 | return option
58 | }
59 | const getLoadingOption = () => {
60 | const option = {
61 | text: '加载中...',
62 | color: '#4413c2',
63 | textColor: '#270240',
64 | maskColor: 'rgba(194, 88, 86, 0.3)',
65 | zlevel: 0,
66 | }
67 | return option
68 | }
69 |
70 | let code =
71 | 'onChartReady: function(chart) {\n' +
72 | " 'chart.hideLoading();\n" +
73 | '}\n\n' +
74 | ''
79 |
80 | return (
81 |
82 |
83 |
88 |
94 |
95 |
96 | {code}
97 |
98 |
99 |
100 | )
101 | }
102 | }
103 |
104 | export default ChartShowLoadingComponent
105 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/ChartWithEventComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 |
4 | const ChartWithEventComponent = () => {
5 | const onChartReady = echart => {
6 | /* eslint-disable */
7 | console.log('echart is ready', echart)
8 | }
9 | const onChartLegendselectchanged = (param, echart) => {
10 | console.log(param, echart)
11 | }
12 | const onChartClick = (param, echart) => {
13 | console.log(param, echart)
14 | }
15 | const getOtion = () => {
16 | const option = {
17 | title: {
18 | text: '某站点用户访问来源',
19 | subtext: '纯属虚构',
20 | x: 'center',
21 | },
22 | tooltip: {
23 | trigger: 'item',
24 | formatter: '{a}
{b} : {c} ({d}%)',
25 | },
26 | legend: {
27 | orient: 'vertical',
28 | left: 'left',
29 | data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎'],
30 | },
31 | series: [
32 | {
33 | name: '访问来源',
34 | type: 'pie',
35 | radius: '55%',
36 | center: ['50%', '60%'],
37 | data: [
38 | { value: 335, name: '直接访问' },
39 | { value: 310, name: '邮件营销' },
40 | { value: 234, name: '联盟广告' },
41 | { value: 135, name: '视频广告' },
42 | { value: 1548, name: '搜索引擎' },
43 | ],
44 | itemStyle: {
45 | emphasis: {
46 | shadowBlur: 10,
47 | shadowOffsetX: 0,
48 | shadowColor: 'rgba(0, 0, 0, 0.5)',
49 | },
50 | },
51 | },
52 | ],
53 | }
54 | return option
55 | }
56 |
57 | let onEvents = {
58 | click: onChartClick,
59 | legendselectchanged: onChartLegendselectchanged,
60 | }
61 | let code =
62 | 'let onEvents = {\n' +
63 | " 'click': onChartClick,\n" +
64 | " 'legendselectchanged': onChartLegendselectchanged\n" +
65 | '}\n\n' +
66 | ''
71 |
72 | return (
73 |
74 |
75 |
80 |
86 |
87 |
88 | {code}
89 |
90 |
91 |
92 | )
93 | }
94 |
95 | export default ChartWithEventComponent
96 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/EchartsComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import SimpleChartComponent from './SimpleChartComponent'
5 | import ChartWithEventComponent from './ChartWithEventComponent'
6 | import ThemeChartComponent from './ThemeChartComponent'
7 | import ChartShowLoadingComponent from './ChartShowLoadingComponent'
8 | import ChartAPIComponent from './ChartAPIComponent'
9 | import DynamicChartComponent from './DynamicChartComponent'
10 | import MapChartComponent from './MapChartComponent'
11 |
12 | // v1.2.0 add 7 demo.
13 | import AirportCoordComponent from './AirportCoordComponent'
14 | import CalendarComponent from './CalendarComponent'
15 | import GaugeComponent from './GaugeComponent'
16 | import GCalendarComponent from './GCalendarComponent'
17 | import GraphComponent from './GraphComponent'
18 | import LunarCalendarComponent from './LunarCalendarComponent'
19 | import TreemapComponent from './TreemapComponent'
20 | import LiquidfillComponent from './LiquidfillComponent'
21 | import BubbleGradientComponent from './BubbleGradientComponent'
22 | import TransparentBar3DComPonent from './TransparentBar3DComPonent'
23 |
24 | const EchartsComponent = ({ type }) => {
25 | if (type === 'simple') return
26 | if (type === 'loading') return
27 | if (type === 'api') return
28 | if (type === 'events') return
29 | if (type === 'theme') return
30 | if (type === 'dynamic') return
31 | if (type === 'map') return
32 | if (type === 'airport') return
33 | if (type === 'graph') return
34 | if (type === 'calendar') return
35 | if (type === 'treemap') return
36 | if (type === 'gauge') return
37 | if (type === 'gcalendar') return
38 | if (type === 'lunar') return
39 | if (type === 'liquid') return
40 | if (type === 'BubbleGradientComponent') return
41 | if (type === 'TransparentBar3DComPonent') return
42 | return
43 | }
44 |
45 | EchartsComponent.propTypes = {
46 | type: PropTypes.string,
47 | }
48 |
49 | export default EchartsComponent
50 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/GCalendarComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 | import echarts from 'echarts'
4 |
5 | const GCalendarComponent = () => {
6 | const getVirtulData = year => {
7 | year = year || '2017'
8 | let date = +echarts.number.parseDate(`${year}-01-01`)
9 | let end = +echarts.number.parseDate(`${+year + 1}-01-01`)
10 | let dayTime = 3600 * 24 * 1000
11 | let data = []
12 | for (let time = date; time < end; time += dayTime) {
13 | data.push([
14 | echarts.format.formatTime('yyyy-MM-dd', time),
15 | Math.floor(Math.random() * 1000),
16 | ])
17 | }
18 | return data
19 | }
20 |
21 | const option = {
22 | tooltip: {
23 | position: 'top',
24 | },
25 | visualMap: {
26 | min: 0,
27 | max: 1000,
28 | calculable: true,
29 | orient: 'horizontal',
30 | left: 'center',
31 | top: 'top',
32 | },
33 |
34 | calendar: [
35 | {
36 | range: '2017',
37 | cellSize: ['auto', 20],
38 | },
39 | {
40 | top: 260,
41 | range: '2016',
42 | cellSize: ['auto', 20],
43 | },
44 | ],
45 |
46 | series: [
47 | {
48 | type: 'heatmap',
49 | coordinateSystem: 'calendar',
50 | calendarIndex: 0,
51 | data: getVirtulData(2017),
52 | },
53 | {
54 | type: 'heatmap',
55 | coordinateSystem: 'calendar',
56 | calendarIndex: 1,
57 | data: getVirtulData(2016),
58 | },
59 | ],
60 | }
61 |
62 | return (
63 |
64 |
65 |
66 |
71 |
72 |
73 | )
74 | }
75 |
76 | export default GCalendarComponent
77 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/LiquidfillComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 |
4 | require('echarts-liquidfill')
5 |
6 | const LiquidfillComponent = () => {
7 | const option = {
8 | series: [
9 | {
10 | type: 'liquidFill',
11 | data: [0.6],
12 | },
13 | ],
14 | }
15 | return (
16 |
17 |
18 |
19 |
27 |
28 |
29 | )
30 | }
31 |
32 | export default LiquidfillComponent
33 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/MainPageComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import AdSense from 'react-adsense'
3 | import { Link } from 'react-router'
4 | import DynamicChartComponent from './DynamicChartComponent.js'
5 |
6 | const MainPageComponent = () => {
7 | return (
8 |
9 |
echarts-for-react {this.props.params.type}
10 |
11 | {' '}
12 | A very simple echarts(v3.0) wrapper for React.{' '}
13 |
14 | hustcc/echarts-for-react
15 |
16 |
17 |
18 |
19 |
20 |
21 | Simple demo |
22 | Echarts loading |
23 | Echarts API |
24 | Echarts events |
25 | Echarts theme |
26 | Dynamic chart |
27 | Map chart
28 |
29 |
30 | New
31 | :
32 | Airport |
33 | Graph |
34 | Calendar |
35 | Treemap |
36 | Gauge |
37 | GCalendar |
38 | Lunar |
39 | Liquidfill
40 |
41 | {this.props.children ||
}
42 |
43 |
49 |
50 | )
51 | }
52 |
53 | export default MainPageComponent
54 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/ModuleLoadChartComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 |
4 | const ModuleLoadChartComponent = () => {
5 | const option = {
6 | title: { text: 'ECharts 入门示例' },
7 | tooltip: {},
8 | xAxis: {
9 | data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
10 | },
11 | yAxis: {},
12 | series: [
13 | {
14 | name: '销量',
15 | type: 'bar',
16 | data: [5, 20, 36, 10, 10, 20],
17 | },
18 | ],
19 | }
20 |
21 | let code =
22 | '"
27 | return (
28 |
29 |
30 |
36 |
46 |
47 |
48 | {code}
49 |
50 |
51 |
52 | )
53 | }
54 |
55 | export default ModuleLoadChartComponent
56 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/SimpleChartComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 | import './theme/macarons.js'
4 |
5 | const SimpleChartComponent = () => {
6 | const option = {
7 | title: {
8 | text: '堆叠区域图',
9 | },
10 | tooltip: {
11 | trigger: 'axis',
12 | },
13 | legend: {
14 | data: ['邮件营销', '联盟广告', '视频广告'],
15 | },
16 | toolbox: {
17 | feature: {
18 | saveAsImage: {},
19 | },
20 | },
21 | grid: {
22 | left: '3%',
23 | right: '4%',
24 | bottom: '3%',
25 | containLabel: true,
26 | },
27 | xAxis: [
28 | {
29 | type: 'category',
30 | boundaryGap: false,
31 | data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
32 | },
33 | ],
34 | yAxis: [
35 | {
36 | type: 'value',
37 | },
38 | ],
39 | series: [
40 | {
41 | name: '邮件营销',
42 | type: 'line',
43 | stack: '总量',
44 | areaStyle: { normal: {} },
45 | data: [120, 132, 101, 134, 90, 230, 210],
46 | },
47 | {
48 | name: '联盟广告',
49 | type: 'line',
50 | stack: '总量',
51 | areaStyle: { normal: {} },
52 | data: [220, 182, 191, 234, 290, 330, 310],
53 | },
54 | {
55 | name: '视频广告',
56 | type: 'line',
57 | stack: '总量',
58 | areaStyle: { normal: {} },
59 | data: [150, 232, 201, 154, 190, 330, 410],
60 | },
61 | ],
62 | }
63 | let code =
64 | '"
68 | return (
69 |
70 |
71 |
75 |
81 |
82 |
83 | {code}
84 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default SimpleChartComponent
91 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/ThemeChartComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactEcharts from 'echarts-for-react'
3 |
4 | import echarts from 'echarts'
5 |
6 | const ThemeChartComponent = () => {
7 | const option = {
8 | title: {
9 | text: '阶梯瀑布图',
10 | subtext: 'From ExcelHome',
11 | sublink: 'http://e.weibo.com/1341556070/Aj1J2x5a5',
12 | },
13 | tooltip: {
14 | trigger: 'axis',
15 | axisPointer: {
16 | // 坐标轴指示器,坐标轴触发有效
17 | type: 'shadow', // 默认为直线,可选为:'line' | 'shadow'
18 | },
19 | },
20 | legend: {
21 | data: ['支出', '收入'],
22 | },
23 | grid: {
24 | left: '3%',
25 | right: '4%',
26 | bottom: '3%',
27 | containLabel: true,
28 | },
29 | xAxis: {
30 | type: 'category',
31 | splitLine: { show: false },
32 | data: [
33 | '11月1日',
34 | '11月2日',
35 | '11月3日',
36 | '11月4日',
37 | '11月5日',
38 | '11月6日',
39 | '11月7日',
40 | '11月8日',
41 | '11月9日',
42 | '11月10日',
43 | '11月11日',
44 | ],
45 | },
46 | yAxis: {
47 | type: 'value',
48 | },
49 | series: [
50 | {
51 | name: '辅助',
52 | type: 'bar',
53 | stack: '总量',
54 | itemStyle: {
55 | normal: {
56 | barBorderColor: 'rgba(0,0,0,0)',
57 | color: 'rgba(0,0,0,0)',
58 | },
59 | emphasis: {
60 | barBorderColor: 'rgba(0,0,0,0)',
61 | color: 'rgba(0,0,0,0)',
62 | },
63 | },
64 | data: [0, 900, 1245, 1530, 1376, 1376, 1511, 1689, 1856, 1495, 1292],
65 | },
66 | {
67 | name: '收入',
68 | type: 'bar',
69 | stack: '总量',
70 | label: {
71 | normal: {
72 | show: true,
73 | position: 'top',
74 | },
75 | },
76 | data: [900, 345, 393, '-', '-', 135, 178, 286, '-', '-', '-'],
77 | },
78 | {
79 | name: '支出',
80 | type: 'bar',
81 | stack: '总量',
82 | label: {
83 | normal: {
84 | show: true,
85 | position: 'bottom',
86 | },
87 | },
88 | data: ['-', '-', '-', 108, 154, '-', '-', '-', 119, 361, 203],
89 | },
90 | ],
91 | }
92 |
93 | echarts.registerTheme('my_theme', {
94 | backgroundColor: '#f4cccc',
95 | })
96 |
97 | let code =
98 | "echarts.registerTheme('my_theme', {\n" +
99 | " backgroundColor: '#f4cccc'\n" +
100 | '});\n\n' +
101 | '"
104 | return (
105 |
106 |
107 |
113 |
114 |
119 |
120 | {code}
121 |
122 |
123 |
124 | )
125 | }
126 |
127 | export default ThemeChartComponent
128 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Radio } from 'antd'
3 | import { Page } from 'components'
4 | import EchartsComponent from './EchartsComponent'
5 | import styles from './index.less'
6 |
7 | const RadioGroup = Radio.Group
8 |
9 | const chartList = [
10 | {
11 | label: 'SimpleChart',
12 | value: 'simple',
13 | },
14 | {
15 | label: 'ChartShowLoading',
16 | value: 'loading',
17 | },
18 | {
19 | label: 'ChartAPI',
20 | value: 'api',
21 | },
22 | {
23 | label: 'ChartWithEvent',
24 | value: 'events',
25 | },
26 | {
27 | label: 'ThemeChart',
28 | value: 'theme',
29 | },
30 | {
31 | label: 'DynamicChart',
32 | value: 'dynamic',
33 | },
34 | {
35 | label: 'MapChart',
36 | value: 'map',
37 | },
38 | {
39 | label: 'AirportCoord',
40 | value: 'airport',
41 | },
42 | {
43 | label: 'Graph',
44 | value: 'graph',
45 | },
46 | {
47 | label: 'Calendar',
48 | value: 'calendar',
49 | },
50 | {
51 | label: 'Treemap',
52 | value: 'treemap',
53 | },
54 | {
55 | label: 'Gauge',
56 | value: 'gauge',
57 | },
58 | {
59 | label: 'GCalendar',
60 | value: 'gcalendar',
61 | },
62 | {
63 | label: 'LunarCalendar',
64 | value: 'lunar',
65 | },
66 | {
67 | label: 'Liquidfill',
68 | value: 'liquid',
69 | },
70 | {
71 | label: 'BubbleGradient',
72 | value: 'BubbleGradientComponent',
73 | },
74 | {
75 | label: 'TransparentBar3D',
76 | value: 'TransparentBar3DComPonent',
77 | },
78 | ]
79 |
80 | class Chart extends React.Component {
81 | constructor() {
82 | super()
83 | this.state = {
84 | type: '',
85 | }
86 | this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this)
87 | }
88 | handleRadioGroupChange(e) {
89 | this.setState({
90 | type: e.target.value,
91 | })
92 | }
93 | render() {
94 | return (
95 |
96 |
101 |
102 |
103 |
104 |
110 |
111 | )
112 | }
113 | }
114 |
115 | export default Chart
116 |
--------------------------------------------------------------------------------
/src/pages/chart/ECharts/index.less:
--------------------------------------------------------------------------------
1 | .chart {
2 | label {
3 | margin: 24px 0;
4 | display: block;
5 | font-size: 14px;
6 | }
7 |
8 | pre {
9 | padding: 16px;
10 | overflow: auto;
11 | font-size: 12px;
12 | line-height: 2;
13 | background-color: #f6f8fa;
14 | border-radius: 3px;
15 |
16 | code {
17 | display: inline;
18 | max-width: auto;
19 | padding: 0;
20 | margin: 0;
21 | overflow: visible;
22 | line-height: inherit;
23 | word-wrap: normal;
24 | background-color: transparent;
25 | border: 0;
26 | }
27 | }
28 | }
29 |
30 | :global {
31 | .ant-radio-wrapper {
32 | margin-bottom: 16px;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/pages/chart/Recharts/Container.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { ResponsiveContainer } from 'recharts'
4 | import styles from './Container.less'
5 |
6 | const Container = ({
7 | children,
8 | ratio = 5 / 2,
9 | minHeight = 250,
10 | maxHeight = 350,
11 | }) => (
12 |
13 |
14 |
15 | {children}
16 |
17 |
18 | )
19 |
20 | Container.propTypes = {
21 | children: PropTypes.element.isRequired,
22 | ratio: PropTypes.number,
23 | minHeight: PropTypes.number,
24 | maxHeight: PropTypes.number,
25 | }
26 |
27 | export default Container
28 |
--------------------------------------------------------------------------------
/src/pages/chart/Recharts/Container.less:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | position: relative;
4 | display: inline-block;
5 |
6 | :global {
7 | .recharts-responsive-container {
8 | width: e('calc(100% + 56px)') !important;
9 | margin-left: -32px;
10 | }
11 | }
12 | }
13 |
14 | .content {
15 | position: absolute;
16 | left: 0;
17 | right: 0;
18 | top: 0;
19 | bottom: 0;
20 | }
21 |
--------------------------------------------------------------------------------
/src/pages/chart/Recharts/ReChartsComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import AreaChartComponent from './AreaChartComponent'
5 | import BarChartComponent from './BarChartComponent'
6 | import LineChartComponent from './LineChartComponent'
7 |
8 | const ReChartsComponent = ({ type }) => {
9 | if (type === 'areaChart') return
10 | if (type === 'barChart') return
11 | return
12 | }
13 |
14 | ReChartsComponent.propTypes = {
15 | type: PropTypes.string,
16 | }
17 |
18 | export default ReChartsComponent
19 |
--------------------------------------------------------------------------------
/src/pages/chart/Recharts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Radio } from 'antd'
3 | import { Page } from 'components'
4 | import ReChartsComponent from './ReChartsComponent'
5 | import styles from './index.less'
6 |
7 | const RadioGroup = Radio.Group
8 |
9 | const chartList = [
10 | {
11 | label: 'lineChart',
12 | value: 'lineChart',
13 | },
14 | {
15 | label: 'barChart',
16 | value: 'barChart',
17 | },
18 | {
19 | label: 'areaChart',
20 | value: 'areaChart',
21 | },
22 | ]
23 |
24 | class Chart extends React.Component {
25 | constructor() {
26 | super()
27 | this.state = {
28 | type: '',
29 | }
30 | this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this)
31 | }
32 | handleRadioGroupChange(e) {
33 | this.setState({
34 | type: e.target.value,
35 | })
36 | }
37 | render() {
38 | return (
39 |
40 |
45 |
46 |
47 |
48 |
49 | )
50 | }
51 | }
52 |
53 | export default Chart
54 |
--------------------------------------------------------------------------------
/src/pages/chart/Recharts/index.less:
--------------------------------------------------------------------------------
1 | .chart {
2 | :global {
3 | .ant-card {
4 | overflow: hidden;
5 | margin-bottom: 24px;
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/pages/chart/highCharts/HighChartsComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import HighstockComponent from './HighstockComponent'
5 | import HighmapsComponent from './HighmapsComponent'
6 | import HighMoreComponent from './HighMoreComponent'
7 |
8 | const HighChartsComponent = ({ type }) => {
9 | if (type === 'Highmaps') return
10 | if (type === 'HighMore') return
11 | return
12 | }
13 |
14 | HighChartsComponent.propTypes = {
15 | type: PropTypes.string,
16 | }
17 |
18 | export default HighChartsComponent
19 |
--------------------------------------------------------------------------------
/src/pages/chart/highCharts/HighMoreComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactHighcharts from 'react-highcharts'
3 | import HighchartsExporting from 'highcharts-exporting'
4 | import HighchartsMore from 'highcharts-more'
5 |
6 | HighchartsMore(ReactHighcharts.Highcharts)
7 | HighchartsExporting(ReactHighcharts.Highcharts)
8 |
9 | const config = {
10 | chart: {
11 | polar: true,
12 | },
13 | xAxis: {
14 | categories: [
15 | 'Jan',
16 | 'Feb',
17 | 'Mar',
18 | 'Apr',
19 | 'May',
20 | 'Jun',
21 | 'Jul',
22 | 'Aug',
23 | 'Sep',
24 | 'Oct',
25 | 'Nov',
26 | 'Dec',
27 | ],
28 | },
29 | series: [
30 | {
31 | data: [
32 | 29.9,
33 | 71.5,
34 | 106.4,
35 | 129.2,
36 | 144.0,
37 | 176.0,
38 | 135.6,
39 | 148.5,
40 | 216.4,
41 | 194.1,
42 | 95.6,
43 | 54.4,
44 | ],
45 | },
46 | ],
47 | }
48 |
49 | const HighMoreComponent = () => {
50 | return
51 | }
52 |
53 | export default HighMoreComponent
54 |
--------------------------------------------------------------------------------
/src/pages/chart/highCharts/HighmapsComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactHighmaps from 'react-highcharts/ReactHighmaps.src'
3 | import maps from './mapdata/europe'
4 |
5 | const config = {
6 | chart: {
7 | spacingBottom: 20,
8 | },
9 | title: {
10 | text: 'Europe time zones',
11 | },
12 |
13 | legend: {
14 | enabled: true,
15 | },
16 |
17 | plotOptions: {
18 | map: {
19 | allAreas: false,
20 | joinBy: ['iso-a2', 'code'],
21 | dataLabels: {
22 | enabled: true,
23 | color: 'white',
24 | style: {
25 | fontWeight: 'bold',
26 | },
27 | },
28 | mapData: maps,
29 | tooltip: {
30 | headerFormat: '',
31 | pointFormat: '{point.name}: {series.name}',
32 | },
33 | },
34 | },
35 |
36 | series: [
37 | {
38 | name: 'UTC',
39 | data: ['IE', 'IS', 'GB', 'PT'].map(code => {
40 | return { code }
41 | }),
42 | },
43 | {
44 | name: 'UTC + 1',
45 | data: [
46 | 'NO',
47 | 'SE',
48 | 'DK',
49 | 'DE',
50 | 'NL',
51 | 'BE',
52 | 'LU',
53 | 'ES',
54 | 'FR',
55 | 'PL',
56 | 'CZ',
57 | 'AT',
58 | 'CH',
59 | 'LI',
60 | 'SK',
61 | 'HU',
62 | 'SI',
63 | 'IT',
64 | 'SM',
65 | 'HR',
66 | 'BA',
67 | 'YF',
68 | 'ME',
69 | 'AL',
70 | 'MK',
71 | ].map(code => {
72 | return { code }
73 | }),
74 | },
75 | ],
76 | }
77 |
78 | const HighmapsComponent = () => {
79 | return
80 | }
81 | export default HighmapsComponent
82 |
--------------------------------------------------------------------------------
/src/pages/chart/highCharts/HighstockComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactHighstock from 'react-highcharts/ReactHighstock.src'
3 |
4 | const data = [
5 | [1220832000000, 22.56],
6 | [1220918400000, 21.67],
7 | [1221004800000, 21.66],
8 | [1221091200000, 21.81],
9 | [1221177600000, 21.28],
10 | [1221436800000, 20.05],
11 | [1221523200000, 19.98],
12 | [1221609600000, 18.26],
13 | [1221696000000, 19.16],
14 | [1221782400000, 20.13],
15 | [1222041600000, 18.72],
16 | [1222128000000, 18.12],
17 | [1222214400000, 18.39],
18 | [1222300800000, 18.85],
19 | [1222387200000, 18.32],
20 | [1222646400000, 15.04],
21 | [1222732800000, 16.24],
22 | [1222819200000, 15.59],
23 | [1222905600000, 14.3],
24 | [1222992000000, 13.87],
25 | [1223251200000, 14.02],
26 | [1223337600000, 12.74],
27 | [1223424000000, 12.83],
28 | [1223510400000, 12.68],
29 | [1223596800000, 13.8],
30 | [1223856000000, 15.75],
31 | [1223942400000, 14.87],
32 | [1224028800000, 13.99],
33 | [1224115200000, 14.56],
34 | [1224201600000, 13.91],
35 | [1224460800000, 14.06],
36 | [1224547200000, 13.07],
37 | [1224633600000, 13.84],
38 | [1224720000000, 14.03],
39 | [1224806400000, 13.77],
40 | [1225065600000, 13.16],
41 | [1225152000000, 14.27],
42 | [1225238400000, 14.94],
43 | [1225324800000, 15.86],
44 | [1225411200000, 15.37],
45 | [1225670400000, 15.28],
46 | [1225756800000, 15.86],
47 | [1225843200000, 14.76],
48 | [1225929600000, 14.16],
49 | [1226016000000, 14.03],
50 | [1226275200000, 13.7],
51 | [1226361600000, 13.54],
52 | [1226448000000, 12.87],
53 | [1226534400000, 13.78],
54 | [1226620800000, 12.89],
55 | [1226880000000, 12.59],
56 | [1226966400000, 12.84],
57 | [1227052800000, 12.33],
58 | [1227139200000, 11.5],
59 | [1227225600000, 11.8],
60 | [1227484800000, 13.28],
61 | [1227571200000, 12.97],
62 | [1227657600000, 13.57],
63 | [1227830400000, 13.24],
64 | [1228089600000, 12.7],
65 | [1228176000000, 13.21],
66 | [1228262400000, 13.7],
67 | [1228348800000, 13.06],
68 | [1228435200000, 13.43],
69 | [1228694400000, 14.25],
70 | [1228780800000, 14.29],
71 | [1228867200000, 14.03],
72 | [1228953600000, 13.57],
73 | [1229040000000, 14.04],
74 | [1229299200000, 13.54],
75 | ]
76 |
77 | const config = {
78 | rangeSelector: {
79 | selected: 1,
80 | },
81 | title: {
82 | text: 'AAPL Stock Price',
83 | },
84 | series: [
85 | {
86 | name: 'AAPL',
87 | data,
88 | tooltip: {
89 | valueDecimals: 2,
90 | },
91 | },
92 | ],
93 | }
94 |
95 | const HighstockComponent = () => {
96 | return
97 | }
98 |
99 | export default HighstockComponent
100 |
--------------------------------------------------------------------------------
/src/pages/chart/highCharts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Radio } from 'antd'
3 | import { Page } from 'components'
4 | import HighChartsComponent from './HighChartsComponent'
5 | import styles from './index.less'
6 |
7 | const RadioGroup = Radio.Group
8 |
9 | const chartList = [
10 | {
11 | label: 'Highstock',
12 | value: 'Highstock',
13 | },
14 | {
15 | label: 'Highmaps',
16 | value: 'Highmaps',
17 | },
18 | {
19 | label: 'HighMore',
20 | value: 'HighMore',
21 | },
22 | ]
23 |
24 | class Chart extends React.Component {
25 | constructor() {
26 | super()
27 | this.state = {
28 | type: '',
29 | }
30 | this.handleRadioGroupChange = this.handleRadioGroupChange.bind(this)
31 | }
32 | handleRadioGroupChange(e) {
33 | this.setState({
34 | type: e.target.value,
35 | })
36 | }
37 | render() {
38 | return (
39 |
40 |
45 |
46 |
47 |
48 |
49 | )
50 | }
51 | }
52 |
53 | export default Chart
54 |
--------------------------------------------------------------------------------
/src/pages/chart/highCharts/index.less:
--------------------------------------------------------------------------------
1 | .chart {
2 | :global {
3 | .ant-radio-wrapper {
4 | margin-bottom: 16px;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/browser.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Tag } from 'antd'
4 | import { Color } from 'utils'
5 | import styles from './browser.less'
6 |
7 | const status = {
8 | 1: {
9 | color: Color.green,
10 | },
11 | 2: {
12 | color: Color.red,
13 | },
14 | 3: {
15 | color: Color.blue,
16 | },
17 | 4: {
18 | color: Color.yellow,
19 | },
20 | }
21 |
22 | function Browser({ data }) {
23 | const columns = [
24 | {
25 | title: 'name',
26 | dataIndex: 'name',
27 | className: styles.name,
28 | },
29 | {
30 | title: 'percent',
31 | dataIndex: 'percent',
32 | className: styles.percent,
33 | render: (text, it) => {text}%,
34 | },
35 | ]
36 | return (
37 | key}
42 | dataSource={data}
43 | />
44 | )
45 | }
46 |
47 | Browser.propTypes = {
48 | data: PropTypes.array,
49 | }
50 |
51 | export default Browser
52 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/browser.less:
--------------------------------------------------------------------------------
1 | .percent {
2 | text-align: right !important;
3 | }
4 |
5 | .name {
6 | text-align: left !important;
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/comments.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Tag } from 'antd'
4 | import { Color } from 'utils'
5 | import styles from './comments.less'
6 |
7 | const status = {
8 | 1: {
9 | color: Color.green,
10 | text: 'APPROVED',
11 | },
12 | 2: {
13 | color: Color.yellow,
14 | text: 'PENDING',
15 | },
16 | 3: {
17 | color: Color.red,
18 | text: 'REJECTED',
19 | },
20 | }
21 |
22 | function Comments({ data }) {
23 | const columns = [
24 | {
25 | title: 'avatar',
26 | dataIndex: 'avatar',
27 | width: 48,
28 | className: styles.avatarcolumn,
29 | render: text => (
30 |
34 | ),
35 | },
36 | {
37 | title: 'content',
38 | dataIndex: 'content',
39 | render: (text, it) => (
40 |
41 |
{it.name}
42 |
{it.content}
43 |
44 | {status[it.status].text}
45 | {it.date}
46 |
47 |
48 | ),
49 | },
50 | ]
51 | return (
52 |
53 |
key}
58 | dataSource={data.filter((item, key) => key < 3)}
59 | />
60 |
61 | )
62 | }
63 |
64 | Comments.propTypes = {
65 | data: PropTypes.array,
66 | }
67 |
68 | export default Comments
69 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/comments.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .comments {
4 | :global .ant-table-thead > tr > th {
5 | background: #fff;
6 | border-bottom: solid 1px @border-color-base;
7 | }
8 |
9 | .avatar {
10 | width: 48px;
11 | height: 48px;
12 | background-position: center;
13 | background-size: cover;
14 | border-radius: 50%;
15 | background: #f8f8f8;
16 | display: inline-block;
17 | }
18 |
19 | .content {
20 | text-align: left;
21 | color: #757575;
22 | }
23 |
24 | .date {
25 | color: #a3a3a3;
26 | line-height: 30px;
27 | }
28 |
29 | .daterow {
30 | display: flex;
31 | justify-content: space-between;
32 | }
33 |
34 | .name {
35 | font-size: 14px;
36 | color: #474747;
37 | text-align: left;
38 | }
39 |
40 | .avatarcolumn {
41 | vertical-align: top;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/completed.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .sales {
4 | .title {
5 | margin-left: 32px;
6 | font-size: 16px;
7 | }
8 | }
9 |
10 | .radiusdot {
11 | width: 12px;
12 | height: 12px;
13 | margin-right: 8px;
14 | border-radius: 50%;
15 | display: inline-block;
16 | }
17 |
18 | .legend {
19 | text-align: right;
20 | color: #999;
21 | font-size: 14px;
22 |
23 | li {
24 | height: 48px;
25 | line-height: 48px;
26 | display: inline-block;
27 |
28 | & + li {
29 | margin-left: 24px;
30 | }
31 | }
32 | }
33 |
34 | .tooltip {
35 | background: #fff;
36 | padding: 20px;
37 | font-size: 14px;
38 |
39 | .tiptitle {
40 | font-weight: 700;
41 | font-size: 16px;
42 | margin-bottom: 8px;
43 | }
44 |
45 | .tipitem {
46 | height: 32px;
47 | line-height: 32px;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/cpu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Color } from 'utils'
4 | import CountUp from 'react-countup'
5 | import {
6 | LineChart,
7 | Line,
8 | XAxis,
9 | YAxis,
10 | CartesianGrid,
11 | ResponsiveContainer,
12 | } from 'recharts'
13 | import styles from './cpu.less'
14 |
15 | const countUpProps = {
16 | start: 0,
17 | duration: 2.75,
18 | useEasing: true,
19 | useGrouping: true,
20 | separator: ',',
21 | }
22 |
23 | function Cpu({ usage = 0, space = 0, cpu = 0, data }) {
24 | return (
25 |
26 |
27 |
28 |
usage
29 |
30 |
31 |
32 |
33 |
34 |
space
35 |
36 |
37 |
38 |
39 |
40 |
cpu
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
59 |
66 |
67 |
68 |
69 | )
70 | }
71 |
72 | Cpu.propTypes = {
73 | data: PropTypes.array,
74 | usage: PropTypes.number,
75 | space: PropTypes.number,
76 | cpu: PropTypes.number,
77 | }
78 |
79 | export default Cpu
80 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/cpu.less:
--------------------------------------------------------------------------------
1 | .cpu {
2 | .number {
3 | display: flex;
4 | height: 64px;
5 | justify-content: space-between;
6 | margin-bottom: 32px;
7 |
8 | .item {
9 | text-align: center;
10 | height: 64px;
11 | width: 100%;
12 | position: relative;
13 |
14 | & + .item {
15 | &::before {
16 | content: '';
17 | display: block;
18 | width: 1px;
19 | height: 40px;
20 | position: absolute;
21 | background: #f5f5f5;
22 | top: 12px;
23 | }
24 | }
25 |
26 | p {
27 | color: #757575;
28 |
29 | &:first-child {
30 | font-size: 16px;
31 | }
32 |
33 | &:last-child {
34 | font-size: 20px;
35 | font-weight: 700;
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/index.js:
--------------------------------------------------------------------------------
1 | import NumberCard from './numberCard'
2 | import Quote from './village-profile'
3 | import Sales from './sales'
4 | import Weather from './weather'
5 | import RecentSales from './recentSales'
6 | import Comments from './comments'
7 | import Completed from './completed'
8 | import Browser from './browser'
9 | import Cpu from './cpu'
10 | import User from './user'
11 |
12 | export {
13 | NumberCard,
14 | Quote,
15 | Sales,
16 | Weather,
17 | RecentSales,
18 | Comments,
19 | Completed,
20 | Browser,
21 | Cpu,
22 | User,
23 | }
24 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/numberCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Icon, Card } from 'antd'
4 | import CountUp from 'react-countup'
5 | import styles from './numberCard.less'
6 |
7 | function NumberCard({ icon, color, title, number, countUp }) {
8 | return (
9 |
14 |
15 |
16 |
{title || 'No Title'}
17 |
18 |
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | NumberCard.propTypes = {
34 | icon: PropTypes.string,
35 | color: PropTypes.string,
36 | title: PropTypes.string,
37 | number: PropTypes.number,
38 | countUp: PropTypes.object,
39 | }
40 |
41 | export default NumberCard
42 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/numberCard.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .numberCard {
4 | padding: 32px;
5 | margin-bottom: 24px;
6 | cursor: pointer;
7 |
8 | .iconWarp {
9 | font-size: 54px;
10 | float: left;
11 | }
12 |
13 | .content {
14 | width: 100%;
15 | padding-left: 78px;
16 |
17 | .title {
18 | line-height: 16px;
19 | font-size: 16px;
20 | margin-bottom: 8px;
21 | height: 16px;
22 | .text-overflow();
23 | }
24 |
25 | .number {
26 | line-height: 32px;
27 | font-size: 24px;
28 | height: 32px;
29 | .text-overflow();
30 | margin-bottom: 0;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/recentSales.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import moment from 'moment'
3 | import PropTypes from 'prop-types'
4 | import { Table, Tag } from 'antd'
5 | import { Color } from 'utils'
6 | import styles from './recentSales.less'
7 |
8 | const status = {
9 | 1: {
10 | color: Color.green,
11 | text: 'SALE',
12 | },
13 | 2: {
14 | color: Color.yellow,
15 | text: 'REJECT',
16 | },
17 | 3: {
18 | color: Color.red,
19 | text: 'TAX',
20 | },
21 | 4: {
22 | color: Color.blue,
23 | text: 'EXTENDED',
24 | },
25 | }
26 |
27 | function RecentSales({ data }) {
28 | const columns = [
29 | {
30 | title: 'NAME',
31 | dataIndex: 'name',
32 | },
33 | {
34 | title: 'STATUS',
35 | dataIndex: 'status',
36 | render: text => {status[text].text},
37 | },
38 | {
39 | title: 'DATE',
40 | dataIndex: 'date',
41 | render: text => moment(text).format('YYYY-MM-DD'),
42 | },
43 | {
44 | title: 'PRICE',
45 | dataIndex: 'price',
46 | render: (text, it) => (
47 | ${text}
48 | ),
49 | },
50 | ]
51 | return (
52 |
53 |
key}
57 | dataSource={data.filter((item, key) => key < 5)}
58 | />
59 |
60 | )
61 | }
62 |
63 | RecentSales.propTypes = {
64 | data: PropTypes.array,
65 | }
66 |
67 | export default RecentSales
68 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/recentSales.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .recentsales {
4 | :global .ant-table-thead > tr > th {
5 | background: #fff;
6 | border-bottom: solid 1px @border-color-base;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/sales.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .sales {
4 | overflow: hidden;
5 | .title {
6 | margin-left: 32px;
7 | font-size: 16px;
8 | }
9 | }
10 |
11 | .radiusdot {
12 | width: 12px;
13 | height: 12px;
14 | margin-right: 8px;
15 | border-radius: 50%;
16 | display: inline-block;
17 | }
18 |
19 | .legend {
20 | text-align: right;
21 | color: #999;
22 | font-size: 14px;
23 |
24 | li {
25 | height: 48px;
26 | line-height: 48px;
27 | display: inline-block;
28 |
29 | & + li {
30 | margin-left: 24px;
31 | }
32 | }
33 | }
34 |
35 | .tooltip {
36 | background: #fff;
37 | padding: 20px;
38 | font-size: 14px;
39 |
40 | .tiptitle {
41 | font-weight: 700;
42 | font-size: 16px;
43 | margin-bottom: 8px;
44 | }
45 |
46 | .tipitem {
47 | height: 32px;
48 | line-height: 32px;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/user-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luzhihua407/family_tree_ui/debf3185f1d2b85e348c75cb80a4b9e3c811631a/src/pages/dashboard/components/user-background.png
--------------------------------------------------------------------------------
/src/pages/dashboard/components/user.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Button, Avatar } from 'antd'
4 | import CountUp from 'react-countup'
5 | import { Color } from 'utils'
6 | import styles from './user.less'
7 |
8 | const countUpProps = {
9 | start: 0,
10 | duration: 2.75,
11 | useEasing: true,
12 | useGrouping: true,
13 | separator: ',',
14 | }
15 |
16 | function User({ avatar, username, sales = 0, sold = 0 }) {
17 | return (
18 |
19 |
20 |
21 |
22 |
{username}
23 |
24 |
25 |
26 |
27 |
EARNING SALES
28 |
29 |
30 |
31 |
32 |
33 |
ITEM SOLD
34 |
35 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 | )
46 | }
47 |
48 | User.propTypes = {
49 | avatar: PropTypes.string,
50 | username: PropTypes.string,
51 | sales: PropTypes.number,
52 | sold: PropTypes.number,
53 | }
54 |
55 | export default User
56 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/user.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .user {
4 | .header {
5 | display: flex;
6 | justify-content: center;
7 | text-align: center;
8 | color: #fff;
9 | height: 200px;
10 | background-size: cover;
11 | align-items: center;
12 |
13 | .headerinner {
14 | z-index: 2;
15 | }
16 |
17 | &::after {
18 | content: '';
19 | background-image: url('./user-background.png');
20 | background-size: cover;
21 | position: absolute;
22 | width: 100%;
23 | height: 200px;
24 | left: 0;
25 | top: 0;
26 | opacity: 0.4;
27 | z-index: 1;
28 | }
29 |
30 | .name {
31 | font-size: 16px;
32 | margin-top: 8px;
33 | }
34 | }
35 |
36 | .number {
37 | display: flex;
38 | height: 116px;
39 | justify-content: space-between;
40 | border-bottom: solid 1px #f5f5f5;
41 |
42 | .item {
43 | text-align: center;
44 | height: 116px;
45 | width: 100%;
46 | position: relative;
47 | padding: 30px 0;
48 |
49 | & + .item {
50 | &::before {
51 | content: '';
52 | display: block;
53 | width: 1px;
54 | height: 116px;
55 | position: absolute;
56 | background: #f5f5f5;
57 | top: 0;
58 | }
59 | }
60 |
61 | p {
62 | color: #757575;
63 |
64 | &:first-child {
65 | font-size: 16px;
66 | }
67 |
68 | &:last-child {
69 | font-size: 20px;
70 | font-weight: 700;
71 | }
72 | }
73 | }
74 | }
75 |
76 | .footer {
77 | height: 116px;
78 | display: flex;
79 | justify-content: center;
80 | align-items: center;
81 |
82 | :global .ant-btn {
83 | color: @purple;
84 | border-color: @purple;
85 | padding: 6px 16px;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/village-profile.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import styles from './village-profile.less'
4 |
5 | function VillageProfile({ data }) {
6 | console.log(data)
7 | return (
8 |
9 |
{data && data.remark}
10 |
11 |
12 |
{data && data.name}
13 |
14 |
15 |
16 | )
17 | }
18 |
19 | VillageProfile.propTypes = {
20 | name: PropTypes.string,
21 | remark: PropTypes.string,
22 | data: PropTypes.object,
23 | }
24 |
25 | export default VillageProfile
26 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/village-profile.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .village {
4 | color: gray;
5 | height: 100%;
6 | width: 100%;
7 | padding: 24px;
8 | font-size: 16px;
9 | font-weight: 700;
10 |
11 | .inner {
12 | text-overflow: ellipsis;
13 | word-wrap: normal;
14 | display: -webkit-box;
15 | -webkit-box-orient: vertical;
16 | -webkit-line-clamp: 4;
17 | overflow: hidden;
18 | text-indent: 24px;
19 | }
20 |
21 | .footer {
22 | position: relative;
23 | margin-top: 14px;
24 |
25 | .description {
26 | width: 100%;
27 |
28 | p {
29 | overflow: hidden;
30 | text-overflow: ellipsis;
31 | white-space: nowrap;
32 | margin-right: 64px;
33 | text-align: right;
34 |
35 | &:last-child {
36 | font-weight: 100;
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/weather.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Spin } from 'antd'
4 | import styles from './weather.less'
5 |
6 | function Weather({ city, icon, dateTime, temperature, name, loading }) {
7 | return (
8 |
9 |
10 |
19 |
20 |
{`${temperature}°`}
21 |
22 | {city},{dateTime}
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | Weather.propTypes = {
31 | city: PropTypes.string,
32 | icon: PropTypes.string,
33 | dateTime: PropTypes.string,
34 | temperature: PropTypes.string,
35 | name: PropTypes.string,
36 | loading: PropTypes.bool,
37 | }
38 |
39 | export default Weather
40 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/weather.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .weather {
4 | color: #fff;
5 | height: 204px;
6 | padding: 24px;
7 | justify-content: space-between;
8 | display: flex;
9 | font-size: 14px;
10 |
11 | .left {
12 | display: flex;
13 | flex-direction: column;
14 | width: 64px;
15 | padding-top: 55px;
16 |
17 | .icon {
18 | width: 64px;
19 | height: 64px;
20 | background-position: center;
21 | background-size: contain;
22 | }
23 |
24 | p {
25 | margin-top: 16px;
26 | }
27 | }
28 |
29 | .right {
30 | display: flex;
31 | flex-direction: column;
32 | width: 50%;
33 |
34 | .temperature {
35 | font-size: 36px;
36 | text-align: right;
37 | height: 64px;
38 | color: #fff;
39 | }
40 |
41 | .description {
42 | overflow: hidden;
43 | text-overflow: ellipsis;
44 | white-space: nowrap;
45 | text-align: right;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/dashboard/index.less:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | position: relative;
3 | :global {
4 | .ant-card {
5 | border-radius: 0;
6 | margin-bottom: 24px;
7 | &:hover {
8 | box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
9 | }
10 | }
11 | .ant-card-body {
12 | overflow-x: hidden;
13 | }
14 | }
15 |
16 | .weather {
17 | &:hover {
18 | box-shadow: 4px 4px 40px rgba(143, 201, 251, 0.6);
19 | }
20 | }
21 |
22 | .quote {
23 | &:hover {
24 | box-shadow: 4px 4px 40px rgba(246, 152, 153, 0.6);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/dashboard/model.js:
--------------------------------------------------------------------------------
1 | import { parse } from 'qs'
2 | import modelExtend from 'dva-model-extend'
3 | import api from 'api'
4 | import { pathMatchRegexp } from 'utils'
5 | import { model } from 'utils/model'
6 |
7 | const { queryDashboard, queryWeather, getOverview, getCurrentVillage } = api
8 |
9 | export default modelExtend(model, {
10 | namespace: 'dashboard',
11 | state: {
12 | numByBranch: [],
13 | numByGender: [],
14 | numByEducation: [],
15 | numByProTeam: [],
16 | genderByGenerations: [],
17 | generationsNames: [],
18 | },
19 | subscriptions: {
20 | setup({ dispatch, history }) {
21 | history.listen(({ pathname }) => {
22 | if (
23 | pathMatchRegexp('/dashboard', pathname) ||
24 | pathMatchRegexp('/', pathname)
25 | ) {
26 | dispatch({ type: 'getOverview' })
27 | dispatch({ type: 'getCurrentVillage' })
28 | }
29 | })
30 | },
31 | },
32 | effects: {
33 | *getOverview({ payload }, { call, put }) {
34 | const data = yield call(getOverview, parse(payload))
35 | if (data.success) {
36 | const rs = data.data
37 | yield put({
38 | type: 'updateState',
39 | payload: rs,
40 | })
41 | }
42 | },
43 | *getCurrentVillage({ payload }, { call, put }) {
44 | const data = yield call(getCurrentVillage, parse(payload))
45 | if (data.success) {
46 | const rs = data.data
47 | yield put({
48 | type: 'updateState',
49 | payload: { villageInfo: rs },
50 | })
51 | }
52 | },
53 | },
54 | })
55 |
--------------------------------------------------------------------------------
/src/pages/dashboard/services/dashboard.js:
--------------------------------------------------------------------------------
1 | import { request, config } from 'utils'
2 |
3 | const { api } = config
4 | const { dashboard } = api
5 |
6 | export function query(params) {
7 | return request({
8 | url: dashboard,
9 | method: 'get',
10 | data: params,
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/dashboard/services/weather.js:
--------------------------------------------------------------------------------
1 | import { request, config } from 'utils'
2 |
3 | const { APIV1 } = config
4 |
5 | export function query(params) {
6 | params.key = 'i7sau1babuzwhycn'
7 | return request({
8 | url: `${APIV1}/weather/now.json`,
9 | method: 'get',
10 | data: params,
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/dict/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal, Button } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 | import styles from './List.less'
6 | import { isAllowed } from '../../auth'
7 |
8 | const { confirm } = Modal
9 |
10 | @withI18n()
11 | class List extends PureComponent {
12 | handleDictClick = (record, e) => {
13 | const { onDeleteItem, onEditItem, i18n } = this.props
14 |
15 | if (e === '1') {
16 | onEditItem(record)
17 | } else if (e === '2') {
18 | confirm({
19 | title: '你确定要删除这条记录吗?',
20 | onOk() {
21 | onDeleteItem(record.id)
22 | },
23 | })
24 | }
25 | }
26 |
27 | render() {
28 | const { onDeleteItem, onEditItem, i18n, ...tableProps } = this.props
29 |
30 | const columns = [
31 | {
32 | title: '编码',
33 | dataIndex: 'code',
34 | key: 'code',
35 | width: '15%',
36 | },
37 | {
38 | title: '名称',
39 | dataIndex: 'name',
40 | key: 'name',
41 | width: '15%',
42 | },
43 | {
44 | title: '数值',
45 | dataIndex: 'numValue',
46 | key: 'numValue',
47 | width: '10%',
48 | },
49 | {
50 | title: '字符值',
51 | dataIndex: 'value',
52 | key: 'value',
53 | width: '10%',
54 | },
55 | {
56 | title: '上级',
57 | dataIndex: 'parentName',
58 | key: 'parentName',
59 | width: '10%',
60 | },
61 | {
62 | title: '备注',
63 | dataIndex: 'remark',
64 | key: 'remark',
65 | width: '15%',
66 | },
67 | {
68 | title: '启用',
69 | dataIndex: 'valid',
70 | key: 'valid',
71 | width: '10%',
72 | },
73 | {
74 | title: '操作',
75 | key: 'operation',
76 | fixed: 'right',
77 | render: (text, record) => {
78 | return (
79 |
80 | {isAllowed('dict.update') && (
81 |
88 | )}
89 | {isAllowed('dict.delete') && (
90 |
97 | )}
98 |
99 | )
100 | },
101 | },
102 | ]
103 |
104 | return (
105 | i18n.t`Total ${total} Items`,
110 | }}
111 | className={styles.table}
112 | bordered
113 | scroll={{ x: '100%' }}
114 | columns={columns}
115 | simple
116 | rowKey={record => record.id}
117 | />
118 | )
119 | }
120 | }
121 |
122 | List.propTypes = {
123 | onDeleteItem: PropTypes.func,
124 | onEditItem: PropTypes.func,
125 | location: PropTypes.object,
126 | }
127 |
128 | export default List
129 |
--------------------------------------------------------------------------------
/src/pages/dict/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/folk/category/components/Filter.js:
--------------------------------------------------------------------------------
1 | /* global document */
2 | import React, { PureComponent } from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Trans, withI18n } from '@lingui/react'
5 | import { Form, Button, Row, Col, DatePicker, Input, Cascader } from 'antd'
6 | import { isAllowed } from '../../../auth'
7 |
8 | const { Search } = Input
9 | const { RangePicker } = DatePicker
10 |
11 | const ColProps = {
12 | xs: 24,
13 | sm: 12,
14 | style: {
15 | marginBottom: 16,
16 | },
17 | }
18 |
19 | const TwoColProps = {
20 | ...ColProps,
21 | xl: 96,
22 | }
23 |
24 | @withI18n()
25 | @Form.create()
26 | class Filter extends PureComponent {
27 | handleFields = fields => {
28 | return fields
29 | }
30 |
31 | handleSubmit = () => {
32 | const { onFilterChange, form } = this.props
33 | const { getFieldsValue } = form
34 |
35 | let fields = getFieldsValue()
36 | fields = this.handleFields(fields)
37 | fields = { param: fields }
38 | onFilterChange(fields)
39 | }
40 |
41 | handleReset = () => {
42 | const { form } = this.props
43 | const { getFieldsValue, setFieldsValue } = form
44 |
45 | const fields = getFieldsValue()
46 | for (let item in fields) {
47 | if ({}.hasOwnProperty.call(fields, item)) {
48 | if (fields[item] instanceof Array) {
49 | fields[item] = []
50 | } else {
51 | fields[item] = undefined
52 | }
53 | }
54 | }
55 | setFieldsValue(fields)
56 | this.handleSubmit()
57 | }
58 | handleChange = (key, values) => {
59 | const { form, onFilterChange } = this.props
60 | const { getFieldsValue } = form
61 |
62 | let fields = getFieldsValue()
63 | fields[key] = values
64 | fields = this.handleFields(fields)
65 | onFilterChange(fields)
66 | }
67 |
68 | render() {
69 | const { onAdd, filter, form, i18n } = this.props
70 | const { getFieldDecorator } = form
71 | const { name } = filter
72 |
73 | return (
74 |
75 |
76 | {getFieldDecorator('name', { initialValue: name })(
77 |
78 | )}
79 |
80 |
86 |
87 |
88 |
96 |
99 | {isAllowed('category.add') && (
100 |
103 | )}
104 |
105 |
106 |
107 |
108 | )
109 | }
110 | }
111 |
112 | Filter.propTypes = {
113 | onAdd: PropTypes.func,
114 | form: PropTypes.object,
115 | filter: PropTypes.object,
116 | onFilterChange: PropTypes.func,
117 | }
118 |
119 | export default Filter
120 |
--------------------------------------------------------------------------------
/src/pages/folk/category/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal, Avatar, Button } from 'antd'
4 | import { DropOption } from 'components'
5 | import { Trans, withI18n } from '@lingui/react'
6 | import Link from 'umi/link'
7 | import styles from './List.less'
8 | import { isAllowed } from '../../../auth'
9 |
10 | const { confirm } = Modal
11 |
12 | @withI18n()
13 | class List extends PureComponent {
14 | handleUserClick = (record, e) => {
15 | const { onDeleteItem, onEditItem, i18n } = this.props
16 | if (e === '1') {
17 | onEditItem(record)
18 | } else if (e === '2') {
19 | confirm({
20 | title: '你确定要删除这条记录吗?',
21 | onOk() {
22 | onDeleteItem(record.id)
23 | },
24 | })
25 | }
26 | }
27 |
28 | render() {
29 | const { onDeleteItem, onEditItem, i18n, ...tableProps } = this.props
30 |
31 | const columns = [
32 | {
33 | title: '名称',
34 | dataIndex: 'name',
35 | key: 'name',
36 | width: '25%',
37 | },
38 | {
39 | title: '创建时间',
40 | dataIndex: 'createTime',
41 | key: 'createTime',
42 | width: '25%',
43 | },
44 | {
45 | title: '启用',
46 | dataIndex: 'valid',
47 | key: 'valid',
48 | width: '25%',
49 | },
50 | {
51 | title: '备注',
52 | dataIndex: 'remark',
53 | key: 'remark',
54 | width: '25%',
55 | },
56 | {
57 | title: '操作',
58 | key: 'operation',
59 | fixed: 'right',
60 | render: (text, record) => {
61 | return (
62 |
63 | {isAllowed('category.update') && (
64 |
71 | )}
72 | {isAllowed('category.delete') && (
73 |
80 | )}
81 |
82 | )
83 | },
84 | },
85 | ]
86 |
87 | return (
88 | i18n.t`Total ${total} Items`,
93 | }}
94 | className={styles.table}
95 | bordered
96 | scroll={{ x: '100%' }}
97 | columns={columns}
98 | simple
99 | rowKey={record => record.id}
100 | />
101 | )
102 | }
103 | }
104 |
105 | List.propTypes = {
106 | onDeleteItem: PropTypes.func,
107 | onEditItem: PropTypes.func,
108 | location: PropTypes.object,
109 | }
110 |
111 | export default List
112 |
--------------------------------------------------------------------------------
/src/pages/folk/category/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/folk/category/components/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Form, Input, InputNumber, Radio, Modal, Cascader, Select } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 |
6 | const FormItem = Form.Item
7 |
8 | const formItemLayout = {
9 | labelCol: {
10 | span: 6,
11 | },
12 | wrapperCol: {
13 | span: 14,
14 | },
15 | }
16 | @withI18n()
17 | @Form.create()
18 | class CategoryModal extends PureComponent {
19 | handleOk = () => {
20 | const { item = {}, onOk, form } = this.props
21 | const { validateFields, getFieldsValue } = form
22 |
23 | validateFields(errors => {
24 | if (errors) {
25 | return
26 | }
27 | const data = {
28 | ...getFieldsValue(),
29 | key: item.key,
30 | }
31 | onOk(data)
32 | })
33 | }
34 |
35 | render() {
36 | const {
37 | item = {},
38 | onOk,
39 | form,
40 | i18n,
41 | categoryListData = [],
42 | ...modalProps
43 | } = this.props
44 | console.log(this.props)
45 | const { getFieldDecorator } = form
46 |
47 | return (
48 |
49 |
107 |
108 | )
109 | }
110 | }
111 |
112 | CategoryModal.propTypes = {
113 | type: PropTypes.string,
114 | item: PropTypes.object,
115 | onOk: PropTypes.func,
116 | }
117 |
118 | export default CategoryModal
119 |
--------------------------------------------------------------------------------
/src/pages/folk/categorycontent/$id/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Page } from 'components'
5 | import { PageHeader } from 'antd'
6 |
7 | @connect(({ categoryContentDetail }) => ({ categoryContentDetail }))
8 | class CategoryContentDetail extends PureComponent {
9 | render() {
10 | const { categoryContentDetail } = this.props
11 | const { data } = categoryContentDetail
12 | return (
13 |
14 |
15 |
18 |
19 |
20 | )
21 | }
22 | }
23 | CategoryContentDetail.propTypes = {
24 | categoryContentDetail: PropTypes.object,
25 | }
26 |
27 | export default CategoryContentDetail
28 |
--------------------------------------------------------------------------------
/src/pages/folk/categorycontent/$id/index.less:
--------------------------------------------------------------------------------
1 | .content {
2 | line-height: 2.4;
3 | font-size: 13px;
4 |
5 | .item {
6 | display: flex;
7 |
8 | & > div {
9 | &:first-child {
10 | width: 100px;
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/folk/categorycontent/$id/models/detail.js:
--------------------------------------------------------------------------------
1 | import { pathMatchRegexp } from 'utils'
2 | import api from 'api'
3 | const { queryCategoryContentById } = api
4 |
5 | export default {
6 | namespace: 'categoryContentDetail',
7 | state: {
8 | data: {},
9 | },
10 |
11 | subscriptions: {
12 | setup({ dispatch, history }) {
13 | history.listen(({ pathname }) => {
14 | const match = pathMatchRegexp('/folk/categorycontent/:id', pathname)
15 | if (match) {
16 | dispatch({
17 | type: 'viewCategoryContent',
18 | payload: { id: match[1] },
19 | })
20 | }
21 | })
22 | },
23 | },
24 |
25 | effects: {
26 | *viewCategoryContent({ payload }, { call, put }) {
27 | const result = yield call(queryCategoryContentById, payload)
28 | const { success, message, status, data } = result
29 | if (success) {
30 | yield put({
31 | type: 'updateState',
32 | payload: {
33 | data: data,
34 | },
35 | })
36 | } else {
37 | throw data
38 | }
39 | },
40 | },
41 |
42 | reducers: {
43 | updateState(state, { payload }) {
44 | const { data } = payload
45 | return {
46 | ...state,
47 | data,
48 | }
49 | },
50 | },
51 | }
52 |
--------------------------------------------------------------------------------
/src/pages/folk/categorycontent/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/folk/cemetery/$id/index.js:
--------------------------------------------------------------------------------
1 | import { Descriptions } from 'antd'
2 | import React, { PureComponent } from 'react'
3 | import PropTypes from 'prop-types'
4 | import { connect } from 'dva'
5 | import { Page } from 'components'
6 | import styles from './index.less'
7 |
8 | @connect(({ cemeteryDetail }) => ({ cemeteryDetail }))
9 | class CemeteryDetail extends PureComponent {
10 | render() {
11 | const {
12 | cemeteryDetail: { data },
13 | } = this.props
14 | return (
15 |
16 |
17 | {data.name}
18 | {data.address}
19 | {data.shape}
20 | {data.remark}
21 |
22 |
23 | )
24 | }
25 | }
26 | CemeteryDetail.propTypes = {
27 | cemeteryDetail: PropTypes.object,
28 | }
29 |
30 | export default CemeteryDetail
31 |
--------------------------------------------------------------------------------
/src/pages/folk/cemetery/$id/index.less:
--------------------------------------------------------------------------------
1 | .content {
2 | line-height: 2.4;
3 | font-size: 13px;
4 |
5 | .item {
6 | display: flex;
7 |
8 | & > div {
9 | &:first-child {
10 | width: 100px;
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/folk/cemetery/$id/models/detail.js:
--------------------------------------------------------------------------------
1 | import { pathMatchRegexp } from 'utils'
2 | import api from 'api'
3 | const { queryCemeteryById } = api
4 |
5 | export default {
6 | namespace: 'cemeteryDetail',
7 |
8 | state: {
9 | data: {},
10 | },
11 |
12 | subscriptions: {
13 | setup({ dispatch, history }) {
14 | history.listen(({ pathname }) => {
15 | const match = pathMatchRegexp('/folk/cemetery/:id', pathname)
16 | if (match) {
17 | dispatch({ type: 'get', payload: { id: match[1] } })
18 | }
19 | })
20 | },
21 | },
22 |
23 | effects: {
24 | *get({ payload }, { call, put }) {
25 | console.log(payload)
26 | const result = yield call(queryCemeteryById, payload)
27 | const { success, message, status, data } = result
28 | if (success) {
29 | yield put({
30 | type: 'querySuccess',
31 | payload: {
32 | data: data,
33 | },
34 | })
35 | } else {
36 | throw data
37 | }
38 | },
39 | },
40 |
41 | reducers: {
42 | querySuccess(state, { payload }) {
43 | const { data } = payload
44 | return {
45 | ...state,
46 | data,
47 | }
48 | },
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/folk/cemetery/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal, Avatar, Button } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 | import Link from 'umi/link'
6 | import styles from './List.less'
7 | import { isAllowed } from '../../../auth'
8 | import router from 'umi/router'
9 |
10 | const { confirm } = Modal
11 |
12 | @withI18n()
13 | class List extends PureComponent {
14 | handleClick = (record, e) => {
15 | const { onDeleteItem, onEditItem, i18n } = this.props
16 | if (e === '1') {
17 | onEditItem(record)
18 | } else if (e === '2') {
19 | router.push('/folk/cemetery/' + record.id)
20 | } else if (e === '3') {
21 | confirm({
22 | title: '你确定要删除这条记录吗?',
23 | onOk() {
24 | onDeleteItem(record.id)
25 | },
26 | })
27 | }
28 | }
29 |
30 | render() {
31 | const { onDeleteItem, onEditItem, i18n, ...tableProps } = this.props
32 |
33 | const columns = [
34 | {
35 | title: '祖名',
36 | dataIndex: 'name',
37 | key: 'name',
38 | width: '20%',
39 | },
40 | {
41 | title: '落地点',
42 | dataIndex: 'address',
43 | key: 'address',
44 | width: '25%',
45 | },
46 | {
47 | title: '宝地形状',
48 | dataIndex: 'shape',
49 | key: 'shape',
50 | width: '25%',
51 | },
52 | {
53 | title: '启用',
54 | dataIndex: 'valid',
55 | key: 'valid',
56 | width: '20%',
57 | },
58 | {
59 | title: '操作',
60 | key: 'operation',
61 | fixed: 'right',
62 | render: (text, record) => {
63 | return (
64 |
65 | {isAllowed('cemetery.update') && (
66 |
73 | )}
74 | {isAllowed('cemetery.view') && (
75 |
82 | )}
83 | {isAllowed('cemetery.delete') && (
84 |
91 | )}
92 |
93 | )
94 | },
95 | },
96 | ]
97 |
98 | return (
99 | i18n.t`Total ${total} Items`,
104 | }}
105 | className={styles.table}
106 | bordered
107 | scroll={{ x: '100%' }}
108 | columns={columns}
109 | simple
110 | rowKey={record => record.id}
111 | />
112 | )
113 | }
114 | }
115 |
116 | List.propTypes = {
117 | onDeleteItem: PropTypes.func,
118 | onEditItem: PropTypes.func,
119 | location: PropTypes.object,
120 | }
121 |
122 | export default List
123 |
--------------------------------------------------------------------------------
/src/pages/folk/cemetery/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/folk/cemetery/components/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Form, Input, InputNumber, Radio, Modal, Cascader, Select } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 |
6 | const FormItem = Form.Item
7 |
8 | const formItemLayout = {
9 | labelCol: {
10 | span: 6,
11 | },
12 | wrapperCol: {
13 | span: 14,
14 | },
15 | }
16 | @withI18n()
17 | @Form.create()
18 | class CemeteryModal extends PureComponent {
19 | handleOk = () => {
20 | const { item = {}, onOk, form } = this.props
21 | const { validateFields, getFieldsValue } = form
22 |
23 | validateFields(errors => {
24 | if (errors) {
25 | return
26 | }
27 | const data = {
28 | ...getFieldsValue(),
29 | key: item.key,
30 | }
31 | onOk(data)
32 | })
33 | }
34 |
35 | render() {
36 | const { item = {}, onOk, form, i18n, ...modalProps } = this.props
37 | const { getFieldDecorator } = form
38 |
39 | return (
40 |
41 |
98 |
99 | )
100 | }
101 | }
102 |
103 | CemeteryModal.propTypes = {
104 | type: PropTypes.string,
105 | item: PropTypes.object,
106 | onOk: PropTypes.func,
107 | }
108 |
109 | export default CemeteryModal
110 |
--------------------------------------------------------------------------------
/src/pages/folk/people/$id/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Page } from 'components'
5 | import {
6 | PageHeader,
7 | Descriptions,
8 | Dropdown,
9 | Icon,
10 | Button,
11 | Tag,
12 | Typography,
13 | Row,
14 | } from 'antd'
15 |
16 | @connect(({ peopleDetail }) => ({ peopleDetail }))
17 | class PeopleDetail extends PureComponent {
18 | render() {
19 | const {
20 | peopleDetail: { data },
21 | } = this.props
22 | return (
23 |
24 |
25 | {data.fullName}
26 | {data.nickname}
27 | {data.gender}
28 |
29 | {data.branchName}
30 |
31 |
32 | {data.generationsText}
33 |
34 |
35 | {data.heir}
36 |
37 |
38 | {data.prodTeamName}
39 |
40 |
41 | {data.phoneNumber}
42 |
43 | {data.job}
44 |
45 |
46 | {data.height}
47 |
48 |
49 |
50 | {data.weight}
51 |
52 |
53 | {data.isMarried}
54 |
55 | {data.hasChild}
56 |
57 | {data.education}
58 |
59 | {data.company}
60 |
61 | {data.birth}
62 |
63 | {data.death}
64 |
65 | {data.brief}
66 |
67 | {data.remark}
68 |
69 | {data.valid}
70 |
71 |
72 | )
73 | }
74 | }
75 | PeopleDetail.propTypes = {
76 | peopleDetail: PropTypes.object,
77 | }
78 |
79 | export default PeopleDetail
80 |
--------------------------------------------------------------------------------
/src/pages/folk/people/$id/index.less:
--------------------------------------------------------------------------------
1 | .content {
2 | line-height: 2.4;
3 | font-size: 13px;
4 |
5 | .item {
6 | display: flex;
7 |
8 | & > div {
9 | &:first-child {
10 | width: 100px;
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/folk/people/$id/models/detail.js:
--------------------------------------------------------------------------------
1 | import { pathMatchRegexp } from 'utils'
2 | import api from 'api'
3 | const { viewPeopleById } = api
4 |
5 | export default {
6 | namespace: 'peopleDetail',
7 | state: {
8 | data: {},
9 | },
10 |
11 | subscriptions: {
12 | setup({ dispatch, history }) {
13 | history.listen(({ pathname }) => {
14 | const match = pathMatchRegexp('/folk/people/:id', pathname)
15 | if (match) {
16 | dispatch({ type: 'view', payload: { id: match[1] } })
17 | }
18 | })
19 | },
20 | },
21 |
22 | effects: {
23 | *view({ payload }, { call, put }) {
24 | const result = yield call(viewPeopleById, payload)
25 | const { success, message, status, data } = result
26 | if (success) {
27 | yield put({
28 | type: 'querySuccess',
29 | payload: {
30 | data: data,
31 | },
32 | })
33 | } else {
34 | throw data
35 | }
36 | },
37 | },
38 |
39 | reducers: {
40 | querySuccess(state, { payload }) {
41 | const { data } = payload
42 | return {
43 | ...state,
44 | data,
45 | }
46 | },
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/folk/people/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/folk/relationship/components/Filter.js:
--------------------------------------------------------------------------------
1 | /* global document */
2 | import React, { PureComponent } from 'react'
3 | import PropTypes from 'prop-types'
4 | import { Trans, withI18n } from '@lingui/react'
5 | import { Form, Button, Row, Col, DatePicker, Input, Cascader } from 'antd'
6 | import { isAllowed } from '../../../auth'
7 |
8 | const { Search } = Input
9 | const { RangePicker } = DatePicker
10 |
11 | const ColProps = {
12 | xs: 24,
13 | sm: 12,
14 | style: {
15 | marginBottom: 16,
16 | },
17 | }
18 |
19 | const TwoColProps = {
20 | ...ColProps,
21 | xl: 96,
22 | }
23 |
24 | @withI18n()
25 | @Form.create()
26 | class Filter extends PureComponent {
27 | handleFields = fields => {
28 | return fields
29 | }
30 |
31 | handleSubmit = () => {
32 | const { onFilterChange, form } = this.props
33 | const { getFieldsValue } = form
34 |
35 | let fields = getFieldsValue()
36 | fields = this.handleFields(fields)
37 | fields = { param: fields }
38 | onFilterChange(fields)
39 | }
40 |
41 | handleReset = () => {
42 | const { form } = this.props
43 | const { getFieldsValue, setFieldsValue } = form
44 |
45 | const fields = getFieldsValue()
46 | for (let item in fields) {
47 | if ({}.hasOwnProperty.call(fields, item)) {
48 | if (fields[item] instanceof Array) {
49 | fields[item] = []
50 | } else {
51 | fields[item] = undefined
52 | }
53 | }
54 | }
55 | setFieldsValue(fields)
56 | this.handleSubmit()
57 | }
58 | handleChange = (key, values) => {
59 | const { form, onFilterChange } = this.props
60 | const { getFieldsValue } = form
61 |
62 | let fields = getFieldsValue()
63 | fields[key] = values
64 | fields = this.handleFields(fields)
65 | onFilterChange(fields)
66 | }
67 |
68 | render() {
69 | const { onAdd, filter, form, i18n } = this.props
70 | const { getFieldDecorator } = form
71 | const { name } = filter
72 |
73 | return (
74 |
75 |
76 | {getFieldDecorator('name', { initialValue: name })(
77 |
78 | )}
79 |
80 |
86 |
87 |
88 |
96 |
99 | {isAllowed('relationsship.add') && (
100 |
103 | )}
104 |
105 |
106 |
107 |
108 | )
109 | }
110 | }
111 |
112 | Filter.propTypes = {
113 | onAdd: PropTypes.func,
114 | form: PropTypes.object,
115 | filter: PropTypes.object,
116 | onFilterChange: PropTypes.func,
117 | }
118 |
119 | export default Filter
120 |
--------------------------------------------------------------------------------
/src/pages/folk/relationship/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/folk/tree/index.less:
--------------------------------------------------------------------------------
1 | .placeholder {
2 | width: 100%;
3 | height: 100%;
4 | overflow: auto;
5 | }
6 | .border {
7 | // Set border width.
8 | width: 132px;
9 | height: 72px;
10 | padding: 2px;
11 | border-radius: 5px;
12 | background-color: lightgoldenrodyellow;
13 | border: 1px solid #1c94c4;
14 | }
15 | .title {
16 | padding: 2px;
17 | color: #fff;
18 | text-align: center;
19 | }
20 | .content {
21 | padding: 2px;
22 | //color: #ffffff;
23 | }
24 |
25 | .content .label {
26 | text-align: center;
27 | top: -20px;
28 | left: 40px;
29 | right: 2px;
30 | height: 16px;
31 | }
32 | .label {
33 | position: absolute;
34 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
35 | -webkit-user-select: none;
36 | -webkit-touch-callout: none;
37 | -moz-user-select: none;
38 | -ms-user-select: none;
39 | user-select: none;
40 | -webkit-box-sizing: content-box;
41 | box-sizing: content-box;
42 | color: #333;
43 | }
44 | .CursorFrame {
45 | position: absolute;
46 | font-family: 'Trebuchet MS', Tahoma, Verdana, Arial, sans-serif;
47 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
48 | user-select: none;
49 | box-sizing: content-box;
50 | border-radius: 4px;
51 | border: 2px solid red;
52 | background: rgb(255, 255, 255);
53 | color: rgb(235, 143, 0);
54 | width: 138px;
55 | height: 78px;
56 | left: -2px;
57 | top: -2px;
58 | }
59 |
--------------------------------------------------------------------------------
/src/pages/forgotpassword/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Row, Col, Button, Select, Input, Spin, Form } from 'antd'
5 | import { withI18n } from '@lingui/react'
6 | import styles from '../forgotpassword/index.less'
7 | import { GlobalFooter } from 'ant-design-pro'
8 | import { Link } from 'umi'
9 | const { Option } = Select
10 | const FormItem = Form.Item
11 | const formItemLayout = {
12 | labelCol: {
13 | span: 6,
14 | },
15 | wrapperCol: {
16 | span: 18,
17 | },
18 | }
19 | @withI18n()
20 | @connect(({ signUp, loading }) => ({ signUp, loading }))
21 | @Form.create()
22 | class ForgotPassword extends PureComponent {
23 | state = {
24 | data: [],
25 | value: [],
26 | }
27 | handleOk = () => {
28 | const { item = {}, onOk, form, dispatch } = this.props
29 | const { validateFields, getFieldsValue } = form
30 |
31 | validateFields(errors => {
32 | if (errors) {
33 | return
34 | }
35 | const data = {
36 | ...getFieldsValue(),
37 | }
38 | console.log(data)
39 | dispatch({ type: 'signUp/forgotPassword', payload: data })
40 | })
41 | }
42 |
43 | render() {
44 | const { location, dispatch, forgotPassword, loading, form } = this.props
45 | const { query, pathname } = location
46 | const { getFieldDecorator } = form
47 | const { fetching, data, value } = this.state
48 | return (
49 |
50 |
78 |
79 | )
80 | }
81 | }
82 |
83 | ForgotPassword.propTypes = {
84 | forgotPassword: PropTypes.object,
85 | location: PropTypes.object,
86 | dispatch: PropTypes.func,
87 | loading: PropTypes.object,
88 | }
89 |
90 | export default ForgotPassword
91 |
--------------------------------------------------------------------------------
/src/pages/forgotpassword/index.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .form {
4 | position: absolute;
5 | top: 45%;
6 | left: 50%;
7 | margin: -160px 0 0 -160px;
8 | width: 320px;
9 | height: 320px;
10 | padding: 36px;
11 | box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
12 |
13 | button {
14 | width: 100%;
15 | }
16 |
17 | p {
18 | color: rgb(204, 204, 204);
19 | text-align: center;
20 | margin-top: 16px;
21 | font-size: 12px;
22 | display: flex;
23 | justify-content: space-between;
24 | }
25 | }
26 |
27 | .logo {
28 | text-align: center;
29 | cursor: pointer;
30 | margin-bottom: 24px;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 |
35 | img {
36 | width: 40px;
37 | margin-right: 8px;
38 | }
39 |
40 | span {
41 | vertical-align: text-bottom;
42 | font-size: 16px;
43 | text-transform: uppercase;
44 | display: inline-block;
45 | font-weight: 700;
46 | color: @primary-color;
47 | .text-gradient();
48 | }
49 | }
50 |
51 | .ant-spin-container,
52 | .ant-spin-nested-loading {
53 | height: 100%;
54 | }
55 |
56 | .footer {
57 | position: absolute;
58 | width: 100%;
59 | bottom: 0;
60 | }
61 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import Redirect from 'umi/redirect'
3 | import { withI18n } from '@lingui/react'
4 |
5 | @withI18n()
6 | class Index extends PureComponent {
7 | render() {
8 | const { i18n } = this.props
9 | return
10 | }
11 | }
12 |
13 | export default Index
14 |
--------------------------------------------------------------------------------
/src/pages/login/index.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .form {
4 | position: absolute;
5 | top: 45%;
6 | left: 50%;
7 | margin: -160px 0 0 -160px;
8 | width: 320px;
9 | height: 320px;
10 | padding: 36px;
11 | box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
12 |
13 | button {
14 | width: 100%;
15 | }
16 |
17 | p {
18 | color: rgb(204, 204, 204);
19 | text-align: center;
20 | margin-top: 16px;
21 | font-size: 12px;
22 | display: flex;
23 | justify-content: space-between;
24 | }
25 | }
26 |
27 | .logo {
28 | text-align: center;
29 | cursor: pointer;
30 | margin-bottom: 24px;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 |
35 | img {
36 | width: 40px;
37 | margin-right: 8px;
38 | }
39 |
40 | span {
41 | vertical-align: text-bottom;
42 | font-size: 16px;
43 | text-transform: uppercase;
44 | display: inline-block;
45 | font-weight: 700;
46 | color: @primary-color;
47 | .text-gradient();
48 | }
49 | }
50 |
51 | .ant-spin-container,
52 | .ant-spin-nested-loading {
53 | height: 100%;
54 | }
55 |
56 | .footer {
57 | position: absolute;
58 | width: 100%;
59 | bottom: 0;
60 | }
61 |
--------------------------------------------------------------------------------
/src/pages/login/model.js:
--------------------------------------------------------------------------------
1 | import { router, pathMatchRegexp } from 'utils'
2 | import api from 'api'
3 |
4 | const { loginUser } = api
5 |
6 | export default {
7 | namespace: 'login',
8 |
9 | state: {},
10 |
11 | effects: {
12 | *login({ payload }, { put, call, select }) {
13 | const data = yield call(loginUser, payload)
14 | const { locationQuery } = yield select(_ => _.app)
15 | if (data.success) {
16 | const { from } = locationQuery
17 | yield put({ type: 'app/query', payload: {} })
18 | router.push({
19 | pathname: '/dashboard',
20 | })
21 | if (!pathMatchRegexp('/login', from)) {
22 | if (from === '/') router.push('/dashboard')
23 | else router.push(from)
24 | } else {
25 | router.push('/dashboard')
26 | }
27 | } else {
28 | throw data
29 | }
30 | },
31 | },
32 | }
33 |
--------------------------------------------------------------------------------
/src/pages/post/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import { Table, Avatar } from 'antd'
3 | import { withI18n } from '@lingui/react'
4 | import { Ellipsis } from 'ant-design-pro'
5 | import styles from './List.less'
6 |
7 | @withI18n()
8 | class List extends PureComponent {
9 | render() {
10 | const { i18n, ...tableProps } = this.props
11 | const columns = [
12 | {
13 | title: i18n.t`Image`,
14 | dataIndex: 'image',
15 | render: text => ,
16 | },
17 | {
18 | title: i18n.t`Title`,
19 | dataIndex: 'title',
20 | render: text => (
21 |
22 | {text}
23 |
24 | ),
25 | },
26 | {
27 | title: i18n.t`Author`,
28 | dataIndex: 'author',
29 | },
30 | {
31 | title: i18n.t`Categories`,
32 | dataIndex: 'categories',
33 | },
34 | {
35 | title: i18n.t`Tags`,
36 | dataIndex: 'tags',
37 | },
38 | {
39 | title: i18n.t`Visibility`,
40 | dataIndex: 'visibility',
41 | },
42 | {
43 | title: i18n.t`Comments`,
44 | dataIndex: 'comments',
45 | },
46 | {
47 | title: i18n.t`Views`,
48 | dataIndex: 'views',
49 | },
50 | {
51 | title: i18n.t`Publish Date`,
52 | dataIndex: 'date',
53 | },
54 | ]
55 |
56 | return (
57 | i18n.t`Total ${total} Items`,
62 | }}
63 | bordered
64 | scroll={{ x: 1200 }}
65 | className={styles.table}
66 | columns={columns}
67 | simple
68 | rowKey={record => record.id}
69 | />
70 | )
71 | }
72 | }
73 |
74 | export default List
75 |
--------------------------------------------------------------------------------
/src/pages/post/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/post/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Tabs } from 'antd'
5 | import { router } from 'utils'
6 | import { stringify } from 'qs'
7 | import { withI18n } from '@lingui/react'
8 | import { Page } from 'components'
9 | import List from './components/List'
10 |
11 | const { TabPane } = Tabs
12 |
13 | const EnumPostStatus = {
14 | UNPUBLISH: 1,
15 | PUBLISHED: 2,
16 | }
17 |
18 | @withI18n()
19 | @connect(({ post, loading }) => ({ post, loading }))
20 | class Post extends PureComponent {
21 | render() {
22 | const { post, loading, location, i18n } = this.props
23 | const { list, pagination } = post
24 | const { query, pathname } = location
25 |
26 | const listProps = {
27 | pagination,
28 | dataSource: list,
29 | loading: loading.effects['post/query'],
30 | onChange(page) {
31 | router.push({
32 | pathname,
33 | search: stringify({
34 | ...query,
35 | page: page.current,
36 | pageSize: page.pageSize,
37 | }),
38 | })
39 | },
40 | }
41 |
42 | const handleTabClick = key => {
43 | router.push({
44 | pathname,
45 | search: stringify({
46 | status: key,
47 | }),
48 | })
49 | }
50 |
51 | return (
52 |
53 |
61 |
65 |
66 |
67 |
71 |
72 |
73 |
74 |
75 | )
76 | }
77 | }
78 |
79 | Post.propTypes = {
80 | post: PropTypes.object,
81 | loading: PropTypes.object,
82 | location: PropTypes.object,
83 | dispatch: PropTypes.func,
84 | }
85 |
86 | export default Post
87 |
--------------------------------------------------------------------------------
/src/pages/post/model.js:
--------------------------------------------------------------------------------
1 | import modelExtend from 'dva-model-extend'
2 | import api from 'api'
3 | import { pathMatchRegexp } from 'utils'
4 | import { pageModel } from 'utils/model'
5 |
6 | const { queryPostList } = api
7 |
8 | export default modelExtend(pageModel, {
9 | namespace: 'post',
10 |
11 | subscriptions: {
12 | setup({ dispatch, history }) {
13 | history.listen(location => {
14 | if (pathMatchRegexp('/post', location.pathname)) {
15 | dispatch({
16 | type: 'query',
17 | payload: {
18 | status: 2,
19 | ...location.query,
20 | },
21 | })
22 | }
23 | })
24 | },
25 | },
26 |
27 | effects: {
28 | *query({ payload }, { call, put }) {
29 | const data = yield call(queryPostList, payload)
30 | if (data.success) {
31 | yield put({
32 | type: 'querySuccess',
33 | payload: {
34 | list: data.data,
35 | pagination: {
36 | current: Number(payload.page) || 1,
37 | pageSize: Number(payload.pageSize) || 10,
38 | total: data.total,
39 | },
40 | },
41 | })
42 | } else {
43 | throw data
44 | }
45 | },
46 | },
47 | })
48 |
--------------------------------------------------------------------------------
/src/pages/region/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal, Button } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 | import styles from './List.less'
6 | import { isAllowed } from '../../auth'
7 |
8 | const { confirm } = Modal
9 |
10 | @withI18n()
11 | class List extends PureComponent {
12 | handleRegionClick = (record, e) => {
13 | const { onDeleteItem, onEditItem, i18n } = this.props
14 |
15 | if (e === '1') {
16 | onEditItem(record)
17 | } else if (e === '2') {
18 | confirm({
19 | title: '你确定要删除这条记录吗?',
20 | onOk() {
21 | onDeleteItem(record.id)
22 | },
23 | })
24 | }
25 | }
26 |
27 | render() {
28 | const { onDeleteItem, onEditItem, i18n, ...tableProps } = this.props
29 |
30 | const columns = [
31 | {
32 | title: '区号',
33 | dataIndex: 'areaCode',
34 | key: 'areaCode',
35 | width: '15%',
36 | },
37 | {
38 | title: '编码',
39 | dataIndex: 'code',
40 | key: 'code',
41 | width: '15%',
42 | },
43 | {
44 | title: '简称',
45 | dataIndex: 'shortName',
46 | key: 'shortName',
47 | width: '15%',
48 | },
49 | {
50 | title: '全称',
51 | dataIndex: 'fullName',
52 | key: 'fullName',
53 | width: '15%',
54 | },
55 | {
56 | title: '邮政编码',
57 | dataIndex: 'postCode',
58 | key: 'postCode',
59 | width: '15%',
60 | },
61 | {
62 | title: '启用',
63 | dataIndex: 'valid',
64 | key: 'valid',
65 | width: '15%',
66 | },
67 | {
68 | title: '操作',
69 | key: 'operation',
70 | fixed: 'right',
71 | render: (text, record) => {
72 | return (
73 |
74 | {isAllowed('region.update') && (
75 |
82 | )}
83 | {isAllowed('region.delete') && (
84 |
91 | )}
92 |
93 | )
94 | },
95 | },
96 | ]
97 |
98 | return (
99 | i18n.t`Total ${total} Items`,
104 | }}
105 | className={styles.table}
106 | bordered
107 | scroll={{ x: '100%' }}
108 | columns={columns}
109 | simple
110 | rowKey={record => record.id}
111 | />
112 | )
113 | }
114 | }
115 |
116 | List.propTypes = {
117 | onDeleteItem: PropTypes.func,
118 | onEditItem: PropTypes.func,
119 | location: PropTypes.object,
120 | }
121 |
122 | export default List
123 |
--------------------------------------------------------------------------------
/src/pages/region/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/request/index.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .result {
4 | height: 600px;
5 | width: 100%;
6 | background: @hover-color;
7 | border-color: #ddd;
8 | padding: 16px;
9 | margin-top: 16px;
10 | word-break: break-word;
11 | line-height: 2;
12 | overflow: scroll;
13 | }
14 |
15 | .requestList {
16 | padding-right: 24px;
17 | margin-bottom: 24px;
18 | .listItem {
19 | cursor: pointer;
20 | padding-left: 8px;
21 | &.lstItemActive {
22 | background-color: @hover-color;
23 | }
24 | .background-hover();
25 | }
26 | }
27 |
28 | .paramsBlock {
29 | overflow: visible;
30 | opacity: 1;
31 | height: auto;
32 | transition: opacity 0.3s;
33 | &.hideParams {
34 | width: 0;
35 | height: 0;
36 | opacity: 0;
37 | overflow: hidden;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/pages/security/menu/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 | .editable-cell {
9 | position: relative;
10 | }
11 |
12 | .editable-cell-value-wrap {
13 | padding: 5px 12px;
14 | cursor: pointer;
15 | }
16 |
17 | .editable-row:hover .editable-cell-value-wrap {
18 | border: 1px solid #d9d9d9;
19 | border-radius: 4px;
20 | padding: 4px 11px;
21 | }
22 |
--------------------------------------------------------------------------------
/src/pages/security/role/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/security/role/components/RoleMenuModal.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Form, Input, Tree, Modal } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 |
6 | const FormItem = Form.Item
7 |
8 | const formItemLayout = {
9 | labelCol: {
10 | span: 6,
11 | },
12 | wrapperCol: {
13 | span: 14,
14 | },
15 | }
16 | @withI18n()
17 | @Form.create()
18 | class RoleMenuModal extends PureComponent {
19 | state = {
20 | checkedKeys: [],
21 | }
22 | componentWillReceiveProps(nextProps, nextContext) {
23 | this.setState({ checkedKeys: nextProps.item.menuIds })
24 | }
25 |
26 | handleOk = () => {
27 | const { item = {}, onOk, form } = this.props
28 | const { validateFields, getFieldsValue } = form
29 |
30 | validateFields(errors => {
31 | if (errors) {
32 | return
33 | }
34 | const data = {
35 | ...getFieldsValue(),
36 | key: item.key,
37 | }
38 | data.roleId = item.roleId
39 | data.menuIds = this.state.checkedKeys
40 | onOk(data)
41 | })
42 | }
43 | onEditItem(item) {
44 | dispatch({
45 | type: 'user/hideModal',
46 | })
47 | }
48 | onCheck = (checkedKeys, node, extra) => {
49 | this.setState({ checkedKeys })
50 | this.props.item.menuIds = checkedKeys
51 | }
52 | render() {
53 | const { item = {}, onOk, form, i18n, treeData, ...modalProps } = this.props
54 | const { getFieldDecorator } = form
55 | return (
56 |
57 |
79 |
80 | )
81 | }
82 | }
83 |
84 | RoleMenuModal.propTypes = {
85 | type: PropTypes.string,
86 | item: PropTypes.object,
87 | onOk: PropTypes.func,
88 | }
89 |
90 | export default RoleMenuModal
91 |
--------------------------------------------------------------------------------
/src/pages/security/rolemenu/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal, Avatar } from 'antd'
4 | import { DropOption } from 'components'
5 | import { Trans, withI18n } from '@lingui/react'
6 | import Link from 'umi/link'
7 | import styles from './List.less'
8 |
9 | const { confirm } = Modal
10 |
11 | @withI18n()
12 | class List extends PureComponent {
13 | handleRoleClick = (record, e) => {
14 | const { onDeleteItem, onEditItem, i18n } = this.props
15 |
16 | if (e.key === '1') {
17 | onEditItem(record)
18 | } else if (e.key === '2') {
19 | confirm({
20 | title: '你确定要删除这条记录吗?',
21 | onOk() {
22 | onDeleteItem(record.id)
23 | },
24 | })
25 | }
26 | }
27 |
28 | render() {
29 | const { onDeleteItem, onEditItem, i18n, ...tableProps } = this.props
30 |
31 | const columns = [
32 | {
33 | title: '角色编码',
34 | dataIndex: 'code',
35 | key: 'code',
36 | width: 120,
37 | },
38 | {
39 | title: '角色名称',
40 | dataIndex: 'name',
41 | key: 'name',
42 | width: 120,
43 | },
44 | {
45 | title: '备注',
46 | dataIndex: 'remark',
47 | key: 'remark',
48 | width: 120,
49 | },
50 | {
51 | title: '超级管理员',
52 | dataIndex: 'admin',
53 | key: 'admin',
54 | width: 120,
55 | render: text => {text == 1 ? '是' : '否'},
56 | },
57 | {
58 | title: '创建人',
59 | dataIndex: 'creater',
60 | key: 'creater',
61 | width: 120,
62 | },
63 | {
64 | title: '创建时间',
65 | dataIndex: 'createTime',
66 | key: 'createTime',
67 | width: 120,
68 | },
69 | {
70 | title: '启用',
71 | dataIndex: 'valid',
72 | key: 'valid',
73 | width: 120,
74 | render: text => {text == 1 ? '是' : '否'},
75 | },
76 | {
77 | title: '操作',
78 | key: 'operation',
79 | fixed: 'right',
80 | render: (text, record) => {
81 | return (
82 | this.handleRoleClick(record, e)}
84 | menuOptions={[
85 | { key: '1', name: '修改' },
86 | { key: '2', name: '删除' },
87 | ]}
88 | />
89 | )
90 | },
91 | },
92 | ]
93 |
94 | return (
95 | i18n.t`Total ${total} Items`,
100 | }}
101 | className={styles.table}
102 | bordered
103 | scroll={{ x: 800 }}
104 | columns={columns}
105 | simple
106 | rowKey={record => record.id}
107 | />
108 | )
109 | }
110 | }
111 |
112 | List.propTypes = {
113 | onDeleteItem: PropTypes.func,
114 | onEditItem: PropTypes.func,
115 | location: PropTypes.object,
116 | }
117 |
118 | export default List
119 |
--------------------------------------------------------------------------------
/src/pages/security/rolemenu/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/signup/index.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars';
2 |
3 | .form {
4 | position: absolute;
5 | top: 45%;
6 | left: 50%;
7 | margin: -160px 0 0 -160px;
8 | width: 320px;
9 | height: 400px;
10 | padding: 36px;
11 | box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
12 |
13 | button {
14 | width: 100%;
15 | }
16 |
17 | p {
18 | color: rgb(204, 204, 204);
19 | text-align: center;
20 | margin-top: 16px;
21 | font-size: 12px;
22 | display: flex;
23 | justify-content: space-between;
24 | }
25 | }
26 |
27 | .logo {
28 | text-align: center;
29 | cursor: pointer;
30 | margin-bottom: 24px;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 |
35 | img {
36 | width: 40px;
37 | margin-right: 8px;
38 | }
39 |
40 | span {
41 | vertical-align: text-bottom;
42 | font-size: 16px;
43 | text-transform: uppercase;
44 | display: inline-block;
45 | font-weight: 700;
46 | color: @primary-color;
47 | .text-gradient();
48 | }
49 | }
50 |
51 | .ant-spin-container,
52 | .ant-spin-nested-loading {
53 | height: 100%;
54 | }
55 |
56 | .footer {
57 | position: absolute;
58 | width: 100%;
59 | bottom: 0;
60 | }
61 |
--------------------------------------------------------------------------------
/src/pages/signup/model.js:
--------------------------------------------------------------------------------
1 | import modelExtend from 'dva-model-extend'
2 | import api from 'api'
3 | import router from 'umi/router'
4 | import { message } from 'antd'
5 | const { signUp, getVillageName, forgotPassword } = api
6 |
7 | export default {
8 | namespace: 'signUp',
9 |
10 | state: { data: [] },
11 |
12 | effects: {
13 | *signUp({ payload }, { call, put }) {
14 | const result = yield call(signUp, payload)
15 | const { success, data, msg } = result
16 | if (success) {
17 | message.info('注册成功,正在跳转到登录页')
18 | router.push('/login')
19 | } else {
20 | message.error(msg)
21 | }
22 | },
23 | *getVillageName({ payload }, { call, put }) {
24 | const result = yield call(getVillageName, payload)
25 | const { success, data, msg } = result
26 |
27 | if (success) {
28 | yield put({
29 | type: 'updateState',
30 | payload: {
31 | data: data,
32 | },
33 | })
34 | }
35 | },
36 | *forgotPassword({ payload }, { call, put }) {
37 | console.log(1111)
38 | const result = yield call(forgotPassword, payload)
39 | const { success, data, msg } = result
40 | if (success) {
41 | message.info('注册成功,正在跳转到登录页')
42 | router.push('/login')
43 | } else {
44 | message.error(msg)
45 | }
46 | },
47 | },
48 |
49 | reducers: {},
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/user/$id/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Page } from 'components'
5 | import styles from './index.less'
6 |
7 | @connect(({ userDetail }) => ({ userDetail }))
8 | class UserDetail extends PureComponent {
9 | render() {
10 | const { userDetail } = this.props
11 | const { data } = userDetail
12 | const content = []
13 | for (let key in data) {
14 | if ({}.hasOwnProperty.call(data, key)) {
15 | content.push(
16 |
17 |
{key}
18 |
{String(data[key])}
19 |
20 | )
21 | }
22 | }
23 | return (
24 |
25 | {content}
26 |
27 | )
28 | }
29 | }
30 |
31 | UserDetail.propTypes = {
32 | userDetail: PropTypes.object,
33 | }
34 |
35 | export default UserDetail
36 |
--------------------------------------------------------------------------------
/src/pages/user/$id/index.less:
--------------------------------------------------------------------------------
1 | .content {
2 | line-height: 2.4;
3 | font-size: 13px;
4 |
5 | .item {
6 | display: flex;
7 |
8 | & > div {
9 | &:first-child {
10 | width: 100px;
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/user/$id/models/detail.js:
--------------------------------------------------------------------------------
1 | import { pathMatchRegexp } from 'utils'
2 | import api from 'api'
3 |
4 | const { queryUser } = api
5 |
6 | export default {
7 | namespace: 'userDetail',
8 |
9 | state: {
10 | data: {},
11 | },
12 |
13 | subscriptions: {
14 | setup({ dispatch, history }) {
15 | history.listen(({ pathname }) => {
16 | const match = pathMatchRegexp('/user/:id', pathname)
17 | if (match) {
18 | dispatch({ type: 'query', payload: { id: match[1] } })
19 | }
20 | })
21 | },
22 | },
23 |
24 | effects: {
25 | *query({ payload }, { call, put }) {
26 | const data = yield call(queryUser, payload)
27 | const { success, message, status, ...other } = data
28 | if (success) {
29 | yield put({
30 | type: 'querySuccess',
31 | payload: {
32 | data: other,
33 | },
34 | })
35 | } else {
36 | throw data
37 | }
38 | },
39 | },
40 |
41 | reducers: {
42 | querySuccess(state, { payload }) {
43 | const { data } = payload
44 | return {
45 | ...state,
46 | data,
47 | }
48 | },
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/user/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/user/components/MenuModal.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Form, Input, Tree, Modal } from 'antd'
4 |
5 | const FormItem = Form.Item
6 | const { TreeNode } = Tree
7 |
8 | const formItemLayout = {
9 | labelCol: {
10 | span: 6,
11 | },
12 | wrapperCol: {
13 | span: 14,
14 | },
15 | }
16 | @Form.create()
17 | class MenuModal extends PureComponent {
18 | handleOk = () => {
19 | const { item = {}, onOk, form, userId } = this.props
20 | const { validateFields, getFieldsValue } = form
21 |
22 | validateFields(errors => {
23 | if (errors) {
24 | return
25 | }
26 | const data = {
27 | ...getFieldsValue(),
28 | key: item.key,
29 | }
30 | data.userId = userId
31 | data.menuIds = this.state.checkedKeys
32 | onOk(data)
33 | })
34 | }
35 | state = {
36 | checkedKeys: [],
37 | }
38 | componentWillReceiveProps(nextProps, nextContext) {
39 | this.setState({ checkedKeys: nextProps.item.menuIds })
40 | }
41 | onCheck = (checkedKeys, e) => {
42 | this.setState({ checkedKeys })
43 | this.props.item.menuIds = checkedKeys
44 | }
45 |
46 | renderTreeNodes = data =>
47 | data.map(item => {
48 | if (item.children) {
49 | return (
50 |
51 | {this.renderTreeNodes(item.children)}
52 |
53 | )
54 | }
55 | return
56 | })
57 | render() {
58 | const {
59 | item = {},
60 | onOk,
61 | form,
62 | i18n,
63 | userId,
64 | treeData,
65 | ...menuModalProps
66 | } = this.props
67 | const { getFieldDecorator } = form
68 | return (
69 |
70 |
85 |
86 | )
87 | }
88 | }
89 |
90 | MenuModal.propTypes = {
91 | type: PropTypes.string,
92 | item: PropTypes.object,
93 | onOk: PropTypes.func,
94 | }
95 |
96 | export default MenuModal
97 |
--------------------------------------------------------------------------------
/src/pages/user/components/ResetPasswordModal.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Form, Input, InputNumber, Radio, Modal, Cascader, Select } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 |
6 | const FormItem = Form.Item
7 |
8 | const formItemLayout = {
9 | labelCol: {
10 | span: 6,
11 | },
12 | wrapperCol: {
13 | span: 14,
14 | },
15 | }
16 | @withI18n()
17 | @Form.create()
18 | class UserModal extends PureComponent {
19 | handleOk = () => {
20 | const { item = {}, onOk, form } = this.props
21 | const { validateFields, getFieldsValue } = form
22 |
23 | validateFields(errors => {
24 | if (errors) {
25 | return
26 | }
27 | const data = {
28 | ...getFieldsValue(),
29 | key: item.key,
30 | }
31 | onOk(data)
32 | })
33 | }
34 |
35 | render() {
36 | const {
37 | item = {},
38 | onOk,
39 | form,
40 | i18n,
41 | userId,
42 | ...resetPasswordModalProps
43 | } = this.props
44 | const { getFieldDecorator } = form
45 | return (
46 |
47 |
79 |
80 | )
81 | }
82 | }
83 |
84 | UserModal.propTypes = {
85 | type: PropTypes.string,
86 | item: PropTypes.object,
87 | onOk: PropTypes.func,
88 | }
89 |
90 | export default UserModal
91 |
--------------------------------------------------------------------------------
/src/pages/village/components/List.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Table, Modal, Button } from 'antd'
4 | import { Trans, withI18n } from '@lingui/react'
5 | import styles from './List.less'
6 | import { isAllowed } from '../../auth'
7 |
8 | const { confirm } = Modal
9 |
10 | @withI18n()
11 | class List extends PureComponent {
12 | handleVillageClick = (record, e) => {
13 | const { onDeleteItem, onEditItem, i18n } = this.props
14 |
15 | if (e === '1') {
16 | onEditItem(record)
17 | } else if (e === '2') {
18 | confirm({
19 | title: '你确定要删除这条记录吗?',
20 | onOk() {
21 | onDeleteItem(record.id)
22 | },
23 | })
24 | }
25 | }
26 |
27 | render() {
28 | const { onDeleteItem, onEditItem, i18n, ...tableProps } = this.props
29 |
30 | const columns = [
31 | {
32 | title: '乡村编码',
33 | dataIndex: 'code',
34 | key: 'code',
35 | width: '15%',
36 | },
37 | {
38 | title: '乡村名称',
39 | dataIndex: 'name',
40 | key: 'name',
41 | width: '15%',
42 | },
43 | {
44 | title: '地址',
45 | dataIndex: 'address',
46 | key: 'address',
47 | width: '10%',
48 | },
49 | {
50 | title: '经度',
51 | dataIndex: 'longitude',
52 | key: 'longitude',
53 | width: '10%',
54 | },
55 | {
56 | title: '纬度',
57 | dataIndex: 'latitude',
58 | key: 'latitude',
59 | width: '10%',
60 | },
61 | {
62 | title: '备注',
63 | dataIndex: 'remark',
64 | key: 'remark',
65 | width: '15%',
66 | },
67 | {
68 | title: '启用',
69 | dataIndex: 'valid',
70 | key: 'valid',
71 | width: '10%',
72 | },
73 | {
74 | title: '操作',
75 | key: 'operation',
76 | fixed: 'right',
77 | render: (text, record) => {
78 | return (
79 |
80 | {isAllowed('village.update') && (
81 |
88 | )}
89 | {isAllowed('village.delete') && (
90 |
97 | )}
98 |
99 | )
100 | },
101 | },
102 | ]
103 |
104 | return (
105 | i18n.t`Total ${total} Items`,
110 | }}
111 | className={styles.table}
112 | bordered
113 | scroll={{ x: '100%' }}
114 | columns={columns}
115 | simple
116 | rowKey={record => record.id}
117 | />
118 | )
119 | }
120 | }
121 |
122 | List.propTypes = {
123 | onDeleteItem: PropTypes.func,
124 | onEditItem: PropTypes.func,
125 | location: PropTypes.object,
126 | }
127 |
128 | export default List
129 |
--------------------------------------------------------------------------------
/src/pages/village/components/List.less:
--------------------------------------------------------------------------------
1 | .table {
2 | :global {
3 | .ant-table td {
4 | white-space: nowrap;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/village/images/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import PropTypes from 'prop-types'
3 | import { connect } from 'dva'
4 | import { Empty, Card } from 'antd'
5 | import { withI18n } from '@lingui/react'
6 | import { Page } from 'components'
7 | import { stringify } from 'qs'
8 | @withI18n()
9 | @connect(({ villageImage, loading }) => ({ villageImage, loading }))
10 | class VillageImage extends PureComponent {
11 | render() {
12 | const { Meta } = Card
13 | const { location, dispatch, villageImage, loading, i18n } = this.props
14 | const { images } = villageImage
15 | let empty
16 | if (images.length == 0) {
17 | empty =
18 | }
19 |
20 | return (
21 |
22 | {empty}
23 | {images.map(d => (
24 | }
29 | >
30 |
31 |
32 | ))}
33 |
34 | )
35 | }
36 | }
37 |
38 | VillageImage.propTypes = {
39 | villageImage: PropTypes.object,
40 | location: PropTypes.object,
41 | dispatch: PropTypes.func,
42 | loading: PropTypes.object,
43 | }
44 |
45 | export default VillageImage
46 |
--------------------------------------------------------------------------------
/src/pages/village/images/model.js:
--------------------------------------------------------------------------------
1 | /* global window */
2 | import modelExtend from 'dva-model-extend'
3 | import { pathMatchRegexp } from 'utils'
4 | import api from 'api'
5 | import { pageModel } from 'utils/model'
6 | import { parse } from 'qs'
7 |
8 | const { getVillageImages } = api
9 |
10 | export default modelExtend(pageModel, {
11 | namespace: 'villageImage',
12 |
13 | state: {
14 | images: [],
15 | },
16 |
17 | subscriptions: {
18 | setup({ dispatch, history }) {
19 | history.listen(({ pathname }) => {
20 | if (pathMatchRegexp('/village/images', pathname)) {
21 | dispatch({ type: 'getVillageImages' })
22 | }
23 | })
24 | },
25 | },
26 |
27 | effects: {
28 | *getVillageImages({ payload }, { call, put }) {
29 | const data = yield call(getVillageImages, parse(payload))
30 | if (data.success) {
31 | const rs = data.data
32 | yield put({
33 | type: 'updateState',
34 | payload: rs,
35 | })
36 | }
37 | },
38 | },
39 |
40 | reducers: {},
41 | })
42 |
--------------------------------------------------------------------------------
/src/plugins/onError.js:
--------------------------------------------------------------------------------
1 | import { message } from 'antd'
2 |
3 | export default {
4 | onError(e) {
5 | e.preventDefault()
6 | console.log(e)
7 | if (e.success == false) {
8 | console.log(e.msg == undefined ? '出错了,请联系管理员' : e.msg)
9 | }
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/src/services/index.js:
--------------------------------------------------------------------------------
1 | import request from 'utils/request'
2 | import { apiPrefix } from 'utils/config'
3 |
4 | import api from './api'
5 |
6 | const gen = params => {
7 | let url = apiPrefix + params
8 | let method = 'GET'
9 |
10 | const paramsArray = params.split(' ')
11 | if (paramsArray.length === 2) {
12 | method = paramsArray[0]
13 | url = apiPrefix + paramsArray[1]
14 | }
15 |
16 | return function(data) {
17 | return request({
18 | url,
19 | data,
20 | method,
21 | })
22 | }
23 | }
24 |
25 | const APIFunction = {}
26 | for (const key in api) {
27 | APIFunction[key] = gen(api[key])
28 | }
29 |
30 | APIFunction.queryWeather = params => {
31 | params.key = 'i7sau1babuzwhycn'
32 | return request({
33 | url: `${apiPrefix}/weather/now.json`,
34 | data: params,
35 | })
36 | }
37 |
38 | export default APIFunction
39 |
--------------------------------------------------------------------------------
/src/themes/default.less:
--------------------------------------------------------------------------------
1 | // 本文件是对 ant-design:
2 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
3 | // 相应变量值的覆盖
4 | // 注意:只需写出要覆盖的变量即可(不需要覆盖的变量不要写)
5 |
6 | @import '../../node_modules/antd/lib/style/themes/default.less';
7 |
8 | @border-radius-base: 3px;
9 | @border-radius-sm: 2px;
10 | @shadow-color: rgba(0, 0, 0, 0.05);
11 | @shadow-1-down: 4px 4px 40px @shadow-color;
12 | @border-color-split: #f4f4f4;
13 | @border-color-base: #e5e5e5;
14 | @font-size-base: 13px;
15 | @text-color: #666;
16 | @hover-color: #f9f9fc;
17 |
--------------------------------------------------------------------------------
/src/themes/index.less:
--------------------------------------------------------------------------------
1 | @import '~themes/vars.less';
2 |
3 | body {
4 | height: 100%;
5 | overflow-y: hidden;
6 | background-color: #f8f8f8;
7 | }
8 |
9 | ::-webkit-scrollbar-thumb {
10 | background-color: #e6e6e6;
11 | }
12 |
13 | ::-webkit-scrollbar {
14 | width: 8px;
15 | height: 8px;
16 | }
17 |
18 | :global {
19 | .ant-breadcrumb {
20 | & > span {
21 | &:last-child {
22 | color: #999;
23 | font-weight: normal;
24 | }
25 | }
26 | }
27 |
28 | .ant-breadcrumb-link {
29 | .anticon + span {
30 | margin-left: 4px;
31 | }
32 | }
33 |
34 | .ant-table {
35 | .ant-table-thead > tr > th {
36 | text-align: center;
37 | }
38 |
39 | .ant-table-tbody > tr > td {
40 | text-align: center;
41 | }
42 |
43 | &.ant-table-small {
44 | .ant-table-thead > tr > th {
45 | background: #f7f7f7;
46 | }
47 |
48 | .ant-table-body > table {
49 | padding: 0;
50 | }
51 | }
52 | }
53 |
54 | .ant-table-pagination {
55 | float: none !important;
56 | display: table;
57 | margin: 16px auto !important;
58 | }
59 |
60 | .ant-popover-inner {
61 | border: none;
62 | border-radius: 0;
63 | box-shadow: 0 0 20px rgba(100, 100, 100, 0.2);
64 | }
65 |
66 | .ant-form-item-control {
67 | vertical-align: middle;
68 | }
69 |
70 | .ant-modal-mask {
71 | background-color: rgba(55, 55, 55, 0.2);
72 | }
73 |
74 | .ant-modal-content {
75 | box-shadow: none;
76 | }
77 |
78 | .ant-select-dropdown-menu-item {
79 | padding: 12px 16px !important;
80 | }
81 |
82 | .margin-right {
83 | margin-right: 16px;
84 | }
85 |
86 | a:focus {
87 | text-decoration: none;
88 | }
89 | }
90 | @media (min-width: 1600px) {
91 | :global {
92 | .ant-col-xl-48 {
93 | width: 20%;
94 | }
95 |
96 | .ant-col-xl-96 {
97 | width: 40%;
98 | }
99 | }
100 | }
101 | @media (max-width: 767px) {
102 | :global {
103 | .ant-pagination-item,
104 | .ant-pagination-next,
105 | .ant-pagination-options,
106 | .ant-pagination-prev {
107 | margin-bottom: 8px;
108 | }
109 |
110 | .ant-card {
111 | .ant-card-head {
112 | padding: 0 12px;
113 | }
114 |
115 | .ant-card-body {
116 | padding: 12px;
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/themes/mixin.less:
--------------------------------------------------------------------------------
1 | @import '~themes/default';
2 |
3 | @dark-half: #494949;
4 | @purple: #d897eb;
5 | @shadow-1: 4px 4px 20px 0 rgba(0, 0, 0, 0.01);
6 | @shadow-2: 4px 4px 40px 0 rgba(0, 0, 0, 0.05);
7 | @transition-ease-in: all 0.3s ease-out;
8 | @transition-ease-out: all 0.3s ease-out;
9 | @ease-in: ease-in;
10 |
11 | .text-overflow {
12 | white-space: nowrap;
13 | text-overflow: ellipsis;
14 | overflow: hidden;
15 | }
16 |
17 | .text-gradient {
18 | background-image: -webkit-gradient(
19 | linear,
20 | 37.219838% 34.532506%,
21 | 36.425669% 93.178216%,
22 | from(#29cdff),
23 | to(#0a60ff),
24 | color-stop(0.37, #148eff)
25 | );
26 | -webkit-background-clip: text;
27 | -webkit-text-fill-color: transparent;
28 | }
29 |
30 | .background-hover {
31 | transition: @transition-ease-in;
32 | &:hover {
33 | background-color: @hover-color;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/themes/vars.less:
--------------------------------------------------------------------------------
1 | @import '~themes/default.less';
2 | @import '~themes/mixin.less';
3 |
--------------------------------------------------------------------------------
/src/utils/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | siteName: '盧氏迺炯公后裔族譜',
3 | copyright: '星火 © 2019',
4 | logoPath: '/lu.jpg',
5 | apiPrefix: '/api/v1',
6 | fixedHeader: true, // sticky primary layout header
7 | /* Layout configuration, specify which layout to use for route. */
8 | layouts: [
9 | {
10 | name: 'primary',
11 | include: [/.*/],
12 | exlude: [
13 | /(\/(en|zh))*\/login/,
14 | /(\/(en|zh))*\/signup/,
15 | /(\/(en|zh))*\/forgotPassword/,
16 | ],
17 | },
18 | ],
19 |
20 | /* I18n configuration, `languages` and `defaultLanguage` are required currently. */
21 | i18n: {
22 | /* Countrys flags: https://www.flaticon.com/packs/countrys-flags */
23 | languages: [
24 | {
25 | key: '',
26 | title: '',
27 | flag: '',
28 | },
29 | ],
30 | defaultLanguage: 'zh',
31 | },
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/constant.js:
--------------------------------------------------------------------------------
1 | export const ROLE_TYPE = {
2 | ADMIN: 'admin',
3 | DEFAULT: 'admin',
4 | DEVELOPER: 'developer',
5 | }
6 |
7 | export const CANCEL_REQUEST_MESSAGE = 'cancle request'
8 |
--------------------------------------------------------------------------------
/src/utils/index.test.js:
--------------------------------------------------------------------------------
1 | import { pathMatchRegexp } from './index'
2 | import pathToRegexp from 'path-to-regexp'
3 |
4 | describe('test pathMatchRegexp', () => {
5 | it('get right', () => {
6 | expect(pathMatchRegexp('/user', '/zh/user')).toEqual(
7 | pathToRegexp('/user').exec('/user')
8 | )
9 | expect(pathMatchRegexp('/user', '/user')).toEqual(
10 | pathToRegexp('/user').exec('/user')
11 | )
12 |
13 | expect(pathMatchRegexp('/user/:id', '/zh/user/1')).toEqual(
14 | pathToRegexp('/user/:id').exec('/user/1')
15 | )
16 | expect(pathMatchRegexp('/user/:id', '/user/1')).toEqual(
17 | pathToRegexp('/user/:id').exec('/user/1')
18 | )
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/src/utils/model.js:
--------------------------------------------------------------------------------
1 | import modelExtend from 'dva-model-extend'
2 |
3 | export const model = {
4 | reducers: {
5 | updateState(state, { payload }) {
6 | return {
7 | ...state,
8 | ...payload,
9 | }
10 | },
11 | },
12 | }
13 |
14 | export const pageModel = modelExtend(model, {
15 | state: {
16 | list: [],
17 | pagination: {
18 | showSizeChanger: true,
19 | showQuickJumper: true,
20 | current: 1,
21 | total: 0,
22 | pageSize: 10,
23 | },
24 | },
25 |
26 | reducers: {
27 | querySuccess(state, { payload }) {
28 | const { list, pagination } = payload
29 | return {
30 | ...state,
31 | list,
32 | pagination: {
33 | ...state.pagination,
34 | ...pagination,
35 | },
36 | }
37 | },
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { cloneDeep, isEmpty } from 'lodash'
3 | import pathToRegexp from 'path-to-regexp'
4 | import { message } from 'antd'
5 | import { CANCEL_REQUEST_MESSAGE } from 'utils/constant'
6 | import qs from 'qs'
7 |
8 | const { CancelToken } = axios
9 | window.cancelRequest = new Map()
10 |
11 | export default function request(options) {
12 | let { data, url, method = 'get' } = options
13 | const cloneData = cloneDeep(data)
14 |
15 | try {
16 | let domain = ''
17 | const urlMatch = url.match(/[a-zA-z]+:\/\/[^/]*/)
18 | if (urlMatch) {
19 | ;[domain] = urlMatch
20 | url = url.slice(domain.length)
21 | }
22 |
23 | const match = pathToRegexp.parse(url)
24 | url = pathToRegexp.compile(url)(data)
25 |
26 | for (const item of match) {
27 | if (item instanceof Object && item.name in cloneData) {
28 | delete cloneData[item.name]
29 | }
30 | }
31 | url = domain + url
32 | } catch (e) {
33 | message.error(e.message)
34 | }
35 |
36 | options.cancelToken = new CancelToken(cancel => {
37 | window.cancelRequest.set(Symbol(Date.now()), {
38 | pathname: window.location.pathname,
39 | cancel,
40 | })
41 | })
42 |
43 | // 添加请求拦截器
44 | axios.interceptors.request.use(
45 | function(config) {
46 | let flag = config.url.includes('/oauth/token')
47 | if (flag) {
48 | config.auth = {
49 | username: 'family_tree',
50 | password: 'family_tree',
51 | }
52 | }
53 |
54 | config.headers = {
55 | 'Content-Type': 'application/json',
56 | }
57 | // 在发送请求之前做些什么
58 | return config
59 | },
60 | function(error) {
61 | // 对请求错误做些什么
62 | return Promise.reject(error)
63 | }
64 | )
65 | options.url =
66 | method.toLocaleLowerCase() === 'get'
67 | ? `${url}${isEmpty(cloneData) ? '' : '?'}${qs.stringify(cloneData)}`
68 | : url
69 | return axios(options)
70 | .then(response => {
71 | const { statusText, status, data } = response
72 |
73 | let result = {}
74 | if (typeof data === 'object') {
75 | result = data
76 | if (Array.isArray(data)) {
77 | result.list = data
78 | }
79 | } else {
80 | result.data = data
81 | }
82 |
83 | return Promise.resolve({
84 | success: true,
85 | message: statusText,
86 | statusCode: status,
87 | ...result,
88 | })
89 | })
90 | .catch(error => {
91 | const { response, message } = error
92 |
93 | if (String(message) === CANCEL_REQUEST_MESSAGE) {
94 | return {
95 | success: false,
96 | }
97 | }
98 |
99 | let msg
100 | let statusCode
101 |
102 | if (response && response instanceof Object) {
103 | const { data, statusText } = response
104 | statusCode = response.status
105 | msg = data.message || statusText
106 | } else {
107 | statusCode = 600
108 | msg = error.message || 'Network Error'
109 | }
110 |
111 | /* eslint-disable */
112 | return Promise.reject({
113 | success: false,
114 | statusCode,
115 | message: msg,
116 | })
117 | })
118 | }
119 |
--------------------------------------------------------------------------------
/src/utils/theme.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Color: {
3 | green: '#64ea91',
4 | blue: '#8fc9fb',
5 | purple: '#d897eb',
6 | red: '#f69899',
7 | yellow: '#f8c82e',
8 | peach: '#f797d6',
9 | borderBase: '#e5e5e5',
10 | borderSplit: '#f4f4f4',
11 | grass: '#d6fbb5',
12 | sky: '#c1e0fc',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------