├── mock ├── .gitkeep ├── user.js ├── login.js ├── exception.js ├── utils.js ├── notices.js ├── profile.js ├── chart.js └── rule.js ├── .eslintrcignore ├── .prettierignore ├── .ga ├── README.md ├── public ├── favicon.png ├── fonts │ └── antd │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ └── iconfont.woff └── exImages │ ├── T1YohhXd4bXXXXXXXX.png │ ├── BiazfanxmamNRoxxVxka.png │ ├── ComBAopevLwENQdKWiIn.png │ ├── GvqBnKhFgObvnSGkDsje.png │ ├── HBWnDEUXCnGnGrRfrpKa.png │ ├── OKJXDXrmkNshAMvwtvhu.png │ ├── RzwpdLnhmvDJToTdfDPe.png │ ├── ThXAXghbEsBCCSDihZxY.png │ ├── WdGqmHpayyMjiEhcKoVE.png │ ├── WhxKECPNujWoWEFNdnJE.png │ ├── ZiESqWwCXBRQoaPONSJe.png │ ├── cnrhVkzwxjPwAaCfPbdc.png │ ├── dURIMkkrRFpPgTuzkwnB.png │ ├── gLaIAoVWTtLbBWZNYEMg.png │ ├── gaOngJwsRYRaVAuXXcmB.png │ ├── iZBVOIhGJiAnhplqjvZW.png │ ├── jZUIxmJycoymBprLOUbT.png │ ├── kISTdvpyTAhtGxpovNWd.png │ ├── kZzEzemZyKLKFsojXItE.png │ ├── nxkuOJlFJuAUhzlMTCEe.png │ ├── sBxjgqiuHMGRkIjqlQCd.png │ ├── sfjbOqnsXXJgNCjCzDBL.png │ ├── siCrBXXhmvTQGWPNLBow.png │ ├── tBOxZPlITHqwlGjsJWaF.png │ ├── uMfMFlvUuceEyPpotzlq.png │ ├── uVZonEtjWwmUZPBQfycs.png │ ├── ubnKSIfAJTxIgXOKlciN.png │ ├── zOsKZmFRdUtvpqCImOVY.png │ ├── fcHMVNCjPOsbUGdEduuv.jpeg │ ├── NbuDUAuBlIApFuDvWiND.svg │ ├── ohOEPSYdDTNnyMbGuyLb.svg │ ├── MjEImQtenlyueSmVEfUD.svg │ └── wAhyIChODzsoKIOBHcBk.svg ├── src ├── layouts │ ├── BlankLayout.js │ ├── PageHeaderLayout.less │ ├── PageHeaderLayout.js │ ├── UserLayout.less │ └── UserLayout.js ├── services │ ├── error.js │ ├── user.js │ └── api.js ├── pages │ ├── Exception │ │ ├── style.less │ │ ├── 403.js │ │ ├── 404.js │ │ ├── 500.js │ │ └── triggerException.js │ ├── Profile │ │ ├── BasicProfile.less │ │ └── AdvancedProfile.less │ ├── Result │ │ ├── Success.test.js │ │ ├── Error.js │ │ └── Success.js │ ├── User │ │ ├── RegisterResult.less │ │ ├── Login.less │ │ ├── RegisterResult.js │ │ ├── Register.less │ │ └── Login.js │ ├── Dashboard │ │ ├── Monitor.less │ │ ├── Analysis.less │ │ └── Workplace.less │ ├── List │ │ ├── TableList.less │ │ ├── Applications.less │ │ ├── Projects.less │ │ ├── Articles.less │ │ ├── List.js │ │ ├── CardList.less │ │ ├── CardList.js │ │ ├── BasicList.less │ │ └── BasicList.js │ └── Forms │ │ ├── StepForm │ │ ├── style.less │ │ ├── index.js │ │ ├── Step3.js │ │ ├── Step2.js │ │ └── Step1.js │ │ └── style.less ├── common │ ├── nav │ │ ├── index.js │ │ ├── login.js │ │ └── home.js │ └── menu.js ├── theme.js ├── components │ ├── StandardTable │ │ ├── index.less │ │ └── index.js │ ├── _utils │ │ ├── pathTools.js │ │ └── pathTools.test.js │ ├── EditableLinkGroup │ │ ├── index.less │ │ └── index.js │ ├── Route │ │ └── index.js │ ├── Login │ │ ├── LoginSubmit.js │ │ ├── LoginTab.js │ │ ├── index.less │ │ ├── index.d.ts │ │ ├── index.zh-CN.md │ │ ├── map.js │ │ ├── index.en-US.md │ │ ├── LoginItem.js │ │ ├── index.js │ │ └── demo │ │ │ └── basic.md │ ├── EditableItem │ │ ├── index.less │ │ └── index.js │ ├── ActiveChart │ │ ├── index.less │ │ └── index.js │ ├── ErrorBoundary │ │ └── index.js │ ├── SiderMenu │ │ ├── index.js │ │ ├── SiderMenu.test.js │ │ └── index.less │ ├── StandardFormRow │ │ ├── index.js │ │ └── index.less │ └── GlobalHeader │ │ └── index.less ├── models │ ├── index.js │ ├── monitor.js │ ├── activities.js │ ├── project.js │ ├── error.js │ ├── register.js │ ├── profile.js │ ├── list.js │ ├── rule.js │ ├── user.js │ ├── form.js │ ├── chart.js │ ├── global.js │ └── login.js ├── utils │ ├── authority.js │ ├── Authorized.js │ ├── utils.less │ ├── request.js │ └── utils.js ├── rollbar.js ├── index.ejs ├── e2e │ ├── home.e2e.js │ └── login.e2e.js ├── index.less ├── error.js ├── router.js ├── index.js └── assets │ └── logo.svg ├── .prettierrc ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── appveyor.yml ├── .travis.yml ├── .roadhogrc.mock.js ├── webpack.config.js ├── .stylelintrc ├── LICENSE ├── tests └── run-tests.js ├── .webpackrc.js ├── .roadhogrc.js ├── README.zh-CN.md ├── .eslintrc └── package.json /mock/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrcignore: -------------------------------------------------------------------------------- 1 | public -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg -------------------------------------------------------------------------------- /.ga: -------------------------------------------------------------------------------- 1 | { 2 | "code":"UA-72788897-6" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ant Design Pro Standalone 2 | 3 | - More Info: http://pro.ant.design 4 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/layouts/BlankLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props =>
4 | -------------------------------------------------------------------------------- /public/fonts/antd/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/fonts/antd/iconfont.eot -------------------------------------------------------------------------------- /public/fonts/antd/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/fonts/antd/iconfont.ttf -------------------------------------------------------------------------------- /public/fonts/antd/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/fonts/antd/iconfont.woff -------------------------------------------------------------------------------- /public/exImages/T1YohhXd4bXXXXXXXX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/T1YohhXd4bXXXXXXXX.png -------------------------------------------------------------------------------- /public/exImages/BiazfanxmamNRoxxVxka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/BiazfanxmamNRoxxVxka.png -------------------------------------------------------------------------------- /public/exImages/ComBAopevLwENQdKWiIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/ComBAopevLwENQdKWiIn.png -------------------------------------------------------------------------------- /public/exImages/GvqBnKhFgObvnSGkDsje.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/GvqBnKhFgObvnSGkDsje.png -------------------------------------------------------------------------------- /public/exImages/HBWnDEUXCnGnGrRfrpKa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/HBWnDEUXCnGnGrRfrpKa.png -------------------------------------------------------------------------------- /public/exImages/OKJXDXrmkNshAMvwtvhu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/OKJXDXrmkNshAMvwtvhu.png -------------------------------------------------------------------------------- /public/exImages/RzwpdLnhmvDJToTdfDPe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/RzwpdLnhmvDJToTdfDPe.png -------------------------------------------------------------------------------- /public/exImages/ThXAXghbEsBCCSDihZxY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/ThXAXghbEsBCCSDihZxY.png -------------------------------------------------------------------------------- /public/exImages/WdGqmHpayyMjiEhcKoVE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/WdGqmHpayyMjiEhcKoVE.png -------------------------------------------------------------------------------- /public/exImages/WhxKECPNujWoWEFNdnJE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/WhxKECPNujWoWEFNdnJE.png -------------------------------------------------------------------------------- /public/exImages/ZiESqWwCXBRQoaPONSJe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/ZiESqWwCXBRQoaPONSJe.png -------------------------------------------------------------------------------- /public/exImages/cnrhVkzwxjPwAaCfPbdc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/cnrhVkzwxjPwAaCfPbdc.png -------------------------------------------------------------------------------- /public/exImages/dURIMkkrRFpPgTuzkwnB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/dURIMkkrRFpPgTuzkwnB.png -------------------------------------------------------------------------------- /public/exImages/gLaIAoVWTtLbBWZNYEMg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/gLaIAoVWTtLbBWZNYEMg.png -------------------------------------------------------------------------------- /public/exImages/gaOngJwsRYRaVAuXXcmB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/gaOngJwsRYRaVAuXXcmB.png -------------------------------------------------------------------------------- /public/exImages/iZBVOIhGJiAnhplqjvZW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/iZBVOIhGJiAnhplqjvZW.png -------------------------------------------------------------------------------- /public/exImages/jZUIxmJycoymBprLOUbT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/jZUIxmJycoymBprLOUbT.png -------------------------------------------------------------------------------- /public/exImages/kISTdvpyTAhtGxpovNWd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/kISTdvpyTAhtGxpovNWd.png -------------------------------------------------------------------------------- /public/exImages/kZzEzemZyKLKFsojXItE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/kZzEzemZyKLKFsojXItE.png -------------------------------------------------------------------------------- /public/exImages/nxkuOJlFJuAUhzlMTCEe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/nxkuOJlFJuAUhzlMTCEe.png -------------------------------------------------------------------------------- /public/exImages/sBxjgqiuHMGRkIjqlQCd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/sBxjgqiuHMGRkIjqlQCd.png -------------------------------------------------------------------------------- /public/exImages/sfjbOqnsXXJgNCjCzDBL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/sfjbOqnsXXJgNCjCzDBL.png -------------------------------------------------------------------------------- /public/exImages/siCrBXXhmvTQGWPNLBow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/siCrBXXhmvTQGWPNLBow.png -------------------------------------------------------------------------------- /public/exImages/tBOxZPlITHqwlGjsJWaF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/tBOxZPlITHqwlGjsJWaF.png -------------------------------------------------------------------------------- /public/exImages/uMfMFlvUuceEyPpotzlq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/uMfMFlvUuceEyPpotzlq.png -------------------------------------------------------------------------------- /public/exImages/uVZonEtjWwmUZPBQfycs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/uVZonEtjWwmUZPBQfycs.png -------------------------------------------------------------------------------- /public/exImages/ubnKSIfAJTxIgXOKlciN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/ubnKSIfAJTxIgXOKlciN.png -------------------------------------------------------------------------------- /public/exImages/zOsKZmFRdUtvpqCImOVY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/zOsKZmFRdUtvpqCImOVY.png -------------------------------------------------------------------------------- /public/exImages/fcHMVNCjPOsbUGdEduuv.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyingd/antd-pro-standalone/HEAD/public/exImages/fcHMVNCjPOsbUGdEduuv.jpeg -------------------------------------------------------------------------------- /src/services/error.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | export async function query(code) { 4 | return request(`/api/${code}`) 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/Exception/style.less: -------------------------------------------------------------------------------- 1 | .trigger { 2 | background: 'red'; 3 | :global(.ant-btn) { 4 | margin-right: 8px; 5 | margin-bottom: 12px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/common/nav/index.js: -------------------------------------------------------------------------------- 1 | import Home from './home' 2 | import Login from './login' 3 | 4 | const Router = [Home, Login] 5 | 6 | // nav data 7 | export default Router 8 | -------------------------------------------------------------------------------- /src/pages/Profile/BasicProfile.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .title { 4 | color: @heading-color; 5 | font-size: 16px; 6 | font-weight: 500; 7 | margin-bottom: 16px; 8 | } 9 | -------------------------------------------------------------------------------- /src/services/user.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request' 2 | 3 | export async function query() { 4 | return request('/api/users') 5 | } 6 | 7 | export async function queryCurrent() { 8 | return request('/api/currentUser') 9 | } 10 | -------------------------------------------------------------------------------- /src/layouts/PageHeaderLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .content { 4 | margin: 24px 24px 0; 5 | } 6 | 7 | @media screen and (max-width: @screen-sm) { 8 | .content { 9 | margin: 24px 0 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "semi": false, 5 | "printWidth": 100, 6 | "overrides": [ 7 | { 8 | "files": ".prettierrc", 9 | "options": { "parser": "json" } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less 2 | module.exports = { 3 | // 'primary-color': '#10e99b', 4 | 'card-actions-background': '#f5f8fa', 5 | '@icon-url': '"/fonts/antd/iconfont"', 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/Exception/403.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'dva/router' 3 | import Exception from 'ant-design-pro/lib/Exception' 4 | 5 | export default () => ( 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /src/pages/Exception/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'dva/router' 3 | import Exception from 'ant-design-pro/lib/Exception' 4 | 5 | export default () => ( 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /src/pages/Exception/500.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'dva/router' 3 | import Exception from 'ant-design-pro/lib/Exception' 4 | 5 | export default () => ( 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /src/components/StandardTable/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .standardTable { 4 | :global { 5 | .ant-table-pagination { 6 | margin-top: 24px; 7 | } 8 | } 9 | 10 | .tableAlert { 11 | margin-bottom: 16px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/_utils/pathTools.js: -------------------------------------------------------------------------------- 1 | // /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] 2 | export function urlToList(url) { 3 | const urllist = url.split('/').filter(i => i) 4 | return urllist.map((urlItem, index) => { 5 | return `/${urllist.slice(0, index + 1).join('/')}` 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | // Use require.context to require reducers automatically 2 | // Ref: https://webpack.js.org/guides/dependency-management/#require-context 3 | const context = require.context('./', false, /\.js$/) 4 | export default context 5 | .keys() 6 | .filter(item => item !== './index.js') 7 | .map(key => context(key)) 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/pages/Result/Success.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import Success from './Success' 4 | 5 | it('renders with Result', () => { 6 | const wrapper = shallow() 7 | expect(wrapper.find('Result').length).toBe(1) 8 | expect(wrapper.find('Result').prop('type')).toBe('success') 9 | }) 10 | -------------------------------------------------------------------------------- /src/utils/authority.js: -------------------------------------------------------------------------------- 1 | // use localStorage to store the authority info, which might be sent from server in actual project. 2 | export function getAuthority() { 3 | return localStorage.getItem('antd-pro-authority') || 'admin' 4 | } 5 | 6 | export function setAuthority(authority) { 7 | return localStorage.setItem('antd-pro-authority', authority) 8 | } 9 | -------------------------------------------------------------------------------- /src/pages/User/RegisterResult.less: -------------------------------------------------------------------------------- 1 | .registerResult { 2 | :global { 3 | .anticon { 4 | font-size: 64px; 5 | } 6 | } 7 | .title { 8 | margin-top: 32px; 9 | font-size: 20px; 10 | line-height: 28px; 11 | } 12 | .actions { 13 | margin-top: 40px; 14 | a + a { 15 | margin-left: 8px; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/rollbar.js: -------------------------------------------------------------------------------- 1 | import Rollbar from 'rollbar' 2 | 3 | // Track error by rollbar.com 4 | if (location.host === 'preview.pro.ant.design') { 5 | Rollbar.init({ 6 | accessToken: '033ca6d7c0eb4cc1831cf470c2649971', 7 | captureUncaught: true, 8 | captureUnhandledRejections: true, 9 | payload: { 10 | environment: 'production', 11 | }, 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/components/EditableLinkGroup/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .linkGroup { 4 | padding: 20px 0 8px 24px; 5 | font-size: 0; 6 | & > a { 7 | color: @text-color; 8 | display: inline-block; 9 | font-size: @font-size-base; 10 | margin-bottom: 13px; 11 | width: 25%; 12 | &:hover { 13 | color: @primary-color; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | npm-debug.log* 15 | yarn-error.log 16 | 17 | /coverage 18 | .idea 19 | yarn.lock 20 | 21 | .history 22 | .vscode 23 | 24 | -------------------------------------------------------------------------------- /src/utils/Authorized.js: -------------------------------------------------------------------------------- 1 | import RenderAuthorized from 'ant-design-pro/lib/Authorized' 2 | import { getAuthority } from './authority' 3 | 4 | let Authorized = RenderAuthorized(getAuthority()) // eslint-disable-line 5 | 6 | // Reload the rights component 7 | const reloadAuthorized = () => { 8 | Authorized = RenderAuthorized(getAuthority()) 9 | } 10 | 11 | export { reloadAuthorized } 12 | export default Authorized 13 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ant Design Pro 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "target": "es6", 6 | "jsx": "react", 7 | "sourceMap": true, 8 | "module": "es6", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "baseUrl": "./", 13 | "paths": { 14 | "src/*": [ 15 | "src/*" 16 | ] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Route/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | 3 | const Route = ({ children, name, icon, path, models, page, menu, auth, ...restProps }) => ( 4 | 14 | {children} 15 | 16 | ) 17 | 18 | export default Route 19 | -------------------------------------------------------------------------------- /src/components/Login/LoginSubmit.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | import { Button, Form } from 'antd' 4 | import styles from './index.less' 5 | 6 | const FormItem = Form.Item 7 | 8 | export default ({ className, ...rest }) => { 9 | const clsString = classNames(styles.submit, className) 10 | return ( 11 | 12 | 15 | 16 | 17 | 18 | 19 |
20 | ) 21 | 22 | export default () => ( 23 | 31 | ) 32 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 3 | const cssnano = require('cssnano') 4 | 5 | export default function(config) { 6 | // eslint-disable-next-line 7 | config.entry.vendor = [ 8 | '@babel/polyfill', 9 | 'react', 10 | 'classnames', 11 | 'react-dom', 12 | 'moment', 13 | 'numeral', 14 | 'bizcharts', 15 | '@antv/data-set', 16 | ] 17 | config.plugins.push( 18 | new webpack.optimize.CommonsChunkPlugin({ 19 | name: 'vendor', 20 | minChunks: Infinity, 21 | }) 22 | ) 23 | config.plugins.push( 24 | new OptimizeCssAssetsPlugin({ 25 | assetNameRegExp: /\.css$/g, 26 | cssProcessor: cssnano, 27 | cssProcessorOptions: { discardComments: { removeAll: true } }, 28 | canPrint: true, 29 | }) 30 | ) 31 | return config 32 | } 33 | -------------------------------------------------------------------------------- /src/models/profile.js: -------------------------------------------------------------------------------- 1 | import { queryBasicProfile, queryAdvancedProfile } from '../services/api' 2 | 3 | export default { 4 | namespace: 'profile', 5 | 6 | state: { 7 | basicGoods: [], 8 | advancedOperation1: [], 9 | advancedOperation2: [], 10 | advancedOperation3: [], 11 | }, 12 | 13 | effects: { 14 | *fetchBasic(_, { call, put }) { 15 | const response = yield call(queryBasicProfile) 16 | yield put({ 17 | type: 'show', 18 | payload: response, 19 | }) 20 | }, 21 | *fetchAdvanced(_, { call, put }) { 22 | const response = yield call(queryAdvancedProfile) 23 | yield put({ 24 | type: 'show', 25 | payload: response, 26 | }) 27 | }, 28 | }, 29 | 30 | reducers: { 31 | show(state, { payload }) { 32 | return { 33 | ...state, 34 | ...payload, 35 | } 36 | }, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Login/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .login { 4 | .tabs { 5 | padding: 0 2px; 6 | margin: 0 -2px; 7 | :global { 8 | .ant-tabs-tab { 9 | font-size: 16px; 10 | line-height: 24px; 11 | } 12 | .ant-input-affix-wrapper .ant-input:not(:first-child) { 13 | padding-left: 34px; 14 | } 15 | } 16 | } 17 | 18 | :global { 19 | .ant-tabs .ant-tabs-bar { 20 | border-bottom: 0; 21 | margin-bottom: 24px; 22 | text-align: center; 23 | } 24 | 25 | .ant-form-item { 26 | margin-bottom: 24px; 27 | } 28 | } 29 | 30 | .prefixIcon { 31 | font-size: @font-size-base; 32 | color: @disabled-color; 33 | } 34 | 35 | .getCaptcha { 36 | display: block; 37 | width: 100%; 38 | } 39 | 40 | .submit { 41 | width: 100%; 42 | margin-top: 24px; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mock/login.js: -------------------------------------------------------------------------------- 1 | // import { proxy } from './utils' 2 | 3 | const loginAccount = (req, res) => { 4 | const { password, userName, type } = req.body 5 | if (password === '888888' && userName === 'admin') { 6 | res.send({ 7 | status: 'ok', 8 | type, 9 | currentAuthority: 'admin', 10 | }) 11 | return 12 | } 13 | if (password === '123456' && userName === 'user') { 14 | res.send({ 15 | status: 'ok', 16 | type, 17 | currentAuthority: 'user', 18 | }) 19 | return 20 | } 21 | res.send({ 22 | status: 'error', 23 | type, 24 | currentAuthority: 'guest', 25 | }) 26 | } 27 | const register = (req, res) => { 28 | res.send({ status: 'ok', currentAuthority: 'user' }) 29 | } 30 | 31 | export default { 32 | 'POST /api/register': register, 33 | 'POST /api/login/account': loginAccount, 34 | // 'POST /api/login/account': proxy('http://preview.pro.ant.design/'), 35 | } 36 | -------------------------------------------------------------------------------- /src/pages/User/Register.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .main { 4 | width: 368px; 5 | margin: 0 auto; 6 | 7 | :global { 8 | .ant-form-item { 9 | margin-bottom: 24px; 10 | } 11 | } 12 | 13 | h3 { 14 | font-size: 16px; 15 | margin-bottom: 20px; 16 | } 17 | 18 | .getCaptcha { 19 | display: block; 20 | width: 100%; 21 | } 22 | 23 | .submit { 24 | width: 50%; 25 | } 26 | 27 | .login { 28 | float: right; 29 | line-height: @btn-height-lg; 30 | } 31 | } 32 | 33 | .success, 34 | .warning, 35 | .error { 36 | transition: color 0.3s; 37 | } 38 | 39 | .success { 40 | color: @success-color; 41 | } 42 | 43 | .warning { 44 | color: @warning-color; 45 | } 46 | 47 | .error { 48 | color: @error-color; 49 | } 50 | 51 | .progress-pass > .progress { 52 | :global { 53 | .ant-progress-bg { 54 | background-color: @warning-color; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/pages/Profile/AdvancedProfile.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .headerList { 4 | margin-bottom: 4px; 5 | } 6 | 7 | .tabsCard { 8 | :global { 9 | .ant-card-head { 10 | padding: 0 16px; 11 | } 12 | } 13 | } 14 | 15 | .noData { 16 | color: @disabled-color; 17 | text-align: center; 18 | line-height: 64px; 19 | font-size: 16px; 20 | i { 21 | font-size: 24px; 22 | margin-right: 16px; 23 | position: relative; 24 | top: 3px; 25 | } 26 | } 27 | 28 | .heading { 29 | color: @heading-color; 30 | font-size: 20px; 31 | } 32 | 33 | .stepDescription { 34 | font-size: 14px; 35 | position: relative; 36 | left: 38px; 37 | & > div { 38 | margin-top: 8px; 39 | margin-bottom: 4px; 40 | } 41 | } 42 | 43 | .textSecondary { 44 | color: @text-color-secondary; 45 | } 46 | 47 | @media screen and (max-width: @screen-sm) { 48 | .stepDescription { 49 | left: 8px; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Login/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Button from 'antd/lib/button'; 3 | export interface LoginProps { 4 | defaultActiveKey?: string; 5 | onTabChange?: (key: string) => void; 6 | style?: React.CSSProperties; 7 | onSubmit?: (error: any, values: any) => void; 8 | } 9 | 10 | export interface TabProps { 11 | key?: string; 12 | tab?: React.ReactNode; 13 | } 14 | export class Tab extends React.Component {} 15 | 16 | export interface LoginItemProps { 17 | name?: string; 18 | rules?: any[]; 19 | style?: React.CSSProperties; 20 | onGetCaptcha?: () => void; 21 | } 22 | 23 | export class LoginItem extends React.Component {} 24 | 25 | export default class Login extends React.Component { 26 | static Tab: typeof Tab; 27 | static UserName: typeof LoginItem; 28 | static Password: typeof LoginItem; 29 | static Mobile: typeof LoginItem; 30 | static Captcha: typeof LoginItem; 31 | static Submit: typeof Button; 32 | } 33 | -------------------------------------------------------------------------------- /src/models/list.js: -------------------------------------------------------------------------------- 1 | import { queryFakeList } from '../services/api' 2 | 3 | export default { 4 | namespace: 'list', 5 | 6 | state: { 7 | list: [], 8 | }, 9 | 10 | effects: { 11 | *fetch({ payload }, { call, put }) { 12 | const response = yield call(queryFakeList, payload) 13 | yield put({ 14 | type: 'queryList', 15 | payload: Array.isArray(response) ? response : [], 16 | }) 17 | }, 18 | *appendFetch({ payload }, { call, put }) { 19 | const response = yield call(queryFakeList, payload) 20 | yield put({ 21 | type: 'appendList', 22 | payload: Array.isArray(response) ? response : [], 23 | }) 24 | }, 25 | }, 26 | 27 | reducers: { 28 | queryList(state, action) { 29 | return { 30 | ...state, 31 | list: action.payload, 32 | } 33 | }, 34 | appendList(state, action) { 35 | return { 36 | ...state, 37 | list: state.list.concat(action.payload), 38 | } 39 | }, 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /mock/exception.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'GET /api/500': (req, res) => { 3 | res.status(500).send({ 4 | timestamp: 1513932555104, 5 | status: 500, 6 | error: 'error', 7 | message: 'error', 8 | path: '/base/category/list', 9 | }) 10 | }, 11 | 'GET /api/404': (req, res) => { 12 | res.status(404).send({ 13 | timestamp: 1513932643431, 14 | status: 404, 15 | error: 'Not Found', 16 | message: 'No message available', 17 | path: '/base/category/list/2121212', 18 | }) 19 | }, 20 | 'GET /api/403': (req, res) => { 21 | res.status(403).send({ 22 | timestamp: 1513932555104, 23 | status: 403, 24 | error: 'Unauthorized', 25 | message: 'Unauthorized', 26 | path: '/base/category/list', 27 | }) 28 | }, 29 | 'GET /api/401': (req, res) => { 30 | res.status(401).send({ 31 | timestamp: 1513932555104, 32 | status: 401, 33 | error: 'Unauthorized', 34 | message: 'Unauthorized', 35 | path: '/base/category/list', 36 | }) 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | word-break: break-all; 5 | white-space: nowrap; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | overflow: hidden; 10 | position: relative; 11 | line-height: 1.5em; 12 | max-height: @line * 1.5em; 13 | text-align: justify; 14 | margin-right: -1em; 15 | padding-right: 1em; 16 | &:before { 17 | background: @bg; 18 | content: '...'; 19 | padding: 0 1px; 20 | position: absolute; 21 | right: 14px; 22 | bottom: 0; 23 | } 24 | &:after { 25 | background: white; 26 | content: ''; 27 | margin-top: 0.2em; 28 | position: absolute; 29 | right: 14px; 30 | width: 1em; 31 | height: 1em; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &:before, 40 | &:after { 41 | content: ' '; 42 | display: table; 43 | } 44 | &:after { 45 | clear: both; 46 | visibility: hidden; 47 | font-size: 0; 48 | height: 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/List/TableList.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .tableList { 5 | .tableListOperator { 6 | margin-bottom: 16px; 7 | button { 8 | margin-right: 8px; 9 | } 10 | } 11 | } 12 | 13 | .tableListForm { 14 | :global { 15 | .ant-form-item { 16 | margin-bottom: 24px; 17 | margin-right: 0; 18 | display: flex; 19 | > .ant-form-item-label { 20 | width: auto; 21 | line-height: 32px; 22 | padding-right: 8px; 23 | } 24 | .ant-form-item-control { 25 | line-height: 32px; 26 | } 27 | } 28 | .ant-form-item-control-wrapper { 29 | flex: 1; 30 | } 31 | } 32 | .submitButtons { 33 | white-space: nowrap; 34 | margin-bottom: 24px; 35 | } 36 | } 37 | 38 | @media screen and (max-width: @screen-lg) { 39 | .tableListForm :global(.ant-form-item) { 40 | margin-right: 24px; 41 | } 42 | } 43 | 44 | @media screen and (max-width: @screen-md) { 45 | .tableListForm :global(.ant-form-item) { 46 | margin-right: 8px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "rules": { 4 | "selector-pseudo-class-no-unknown": null, 5 | "shorthand-property-no-redundant-values": null, 6 | "at-rule-empty-line-before": null, 7 | "at-rule-name-space-after": null, 8 | "comment-empty-line-before": null, 9 | "declaration-bang-space-before": null, 10 | "declaration-empty-line-before": null, 11 | "function-comma-newline-after": null, 12 | "function-name-case": null, 13 | "function-parentheses-newline-inside": null, 14 | "function-max-empty-lines": null, 15 | "function-whitespace-after": null, 16 | "number-leading-zero": null, 17 | "number-no-trailing-zeros": null, 18 | "rule-empty-line-before": null, 19 | "selector-combinator-space-after": null, 20 | "selector-descendant-combinator-no-non-space": null, 21 | "selector-list-comma-newline-after": null, 22 | "selector-pseudo-element-colon-notation": null, 23 | "unit-no-unknown": null, 24 | "no-descending-specificity": null, 25 | "value-list-max-empty-lines": null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alipay.inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/models/rule.js: -------------------------------------------------------------------------------- 1 | import { queryRule, removeRule, addRule } from '../services/api' 2 | 3 | export default { 4 | namespace: 'rule', 5 | 6 | state: { 7 | data: { 8 | list: [], 9 | pagination: {}, 10 | }, 11 | }, 12 | 13 | effects: { 14 | *fetch({ payload }, { call, put }) { 15 | const response = yield call(queryRule, payload) 16 | yield put({ 17 | type: 'save', 18 | payload: response, 19 | }) 20 | }, 21 | *add({ payload, callback }, { call, put }) { 22 | const response = yield call(addRule, payload) 23 | yield put({ 24 | type: 'save', 25 | payload: response, 26 | }) 27 | if (callback) callback() 28 | }, 29 | *remove({ payload, callback }, { call, put }) { 30 | const response = yield call(removeRule, payload) 31 | yield put({ 32 | type: 'save', 33 | payload: response, 34 | }) 35 | if (callback) callback() 36 | }, 37 | }, 38 | 39 | reducers: { 40 | save(state, action) { 41 | return { 42 | ...state, 43 | data: action.payload, 44 | } 45 | }, 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | import { query as queryUsers, queryCurrent } from '../services/user' 2 | 3 | export default { 4 | namespace: 'user', 5 | 6 | state: { 7 | list: [], 8 | currentUser: {}, 9 | }, 10 | 11 | effects: { 12 | *fetch(_, { call, put }) { 13 | const response = yield call(queryUsers) 14 | yield put({ 15 | type: 'save', 16 | payload: response, 17 | }) 18 | }, 19 | *fetchCurrent(_, { call, put }) { 20 | const response = yield call(queryCurrent) 21 | yield put({ 22 | type: 'saveCurrentUser', 23 | payload: response, 24 | }) 25 | }, 26 | }, 27 | 28 | reducers: { 29 | save(state, action) { 30 | return { 31 | ...state, 32 | list: action.payload, 33 | } 34 | }, 35 | saveCurrentUser(state, action) { 36 | return { 37 | ...state, 38 | currentUser: action.payload, 39 | } 40 | }, 41 | changeNotifyCount(state, action) { 42 | return { 43 | ...state, 44 | currentUser: { 45 | ...state.currentUser, 46 | notifyCount: action.payload, 47 | }, 48 | } 49 | }, 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /tests/run-tests.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('child_process') 2 | const { kill } = require('cross-port-killer') 3 | 4 | const env = Object.create(process.env) 5 | env.BROWSER = 'none' 6 | const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], { 7 | env, 8 | }) 9 | 10 | startServer.stderr.on('data', data => { 11 | // eslint-disable-next-line 12 | console.log(data); 13 | }) 14 | 15 | startServer.on('exit', () => { 16 | kill(process.env.PORT || 8000) 17 | }) 18 | 19 | // eslint-disable-next-line 20 | console.log('Starting development server for e2e tests...'); 21 | startServer.stdout.on('data', data => { 22 | // eslint-disable-next-line 23 | console.log(data.toString()); 24 | if ( 25 | data.toString().indexOf('Compiled successfully') >= 0 || 26 | data.toString().indexOf('Compiled with warnings') >= 0 27 | ) { 28 | // eslint-disable-next-line 29 | console.log('Development server is started, ready to run tests.'); 30 | const testCmd = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['test'], { 31 | stdio: 'inherit', 32 | }) 33 | testCmd.on('exit', () => { 34 | startServer.kill() 35 | }) 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /src/pages/List/Applications.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .filterCardList { 5 | margin-bottom: -24px; 6 | :global { 7 | .ant-card-meta-content { 8 | margin-top: 0; 9 | } 10 | // disabled white space 11 | .ant-card-meta-avatar { 12 | font-size: 0; 13 | } 14 | .ant-card-actions { 15 | background: #f7f9fa; 16 | } 17 | .ant-list .ant-list-item-content-single { 18 | max-width: 100%; 19 | } 20 | } 21 | .cardInfo { 22 | .clearfix(); 23 | margin-top: 16px; 24 | margin-left: 40px; 25 | & > div { 26 | position: relative; 27 | text-align: left; 28 | float: left; 29 | width: 50%; 30 | p { 31 | line-height: 32px; 32 | font-size: 24px; 33 | margin: 0; 34 | } 35 | p:first-child { 36 | color: @text-color-secondary; 37 | font-size: 12px; 38 | line-height: 20px; 39 | margin-bottom: 4px; 40 | } 41 | } 42 | } 43 | } 44 | 45 | .wan { 46 | position: relative; 47 | top: -2px; 48 | font-size: @font-size-base; 49 | font-style: normal; 50 | line-height: 20px; 51 | margin-left: 2px; 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Login/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Login 3 | subtitle: 登录 4 | cols: 1 5 | order: 15 6 | --- 7 | 8 | 支持多种登录方式切换,内置了几种常见的登录控件,可以灵活组合,也支持和自定义控件配合使用。 9 | 10 | ## API 11 | 12 | ### Login 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | defaultActiveKey | 默认激活 tab 面板的 key | String | - 17 | onTabChange | 切换页签时的回调 | (key) => void | - 18 | onSubmit | 点击提交时的回调 | (err, values) => void | - 19 | 20 | ### Login.Tab 21 | 22 | 参数 | 说明 | 类型 | 默认值 23 | ----|------|-----|------ 24 | key | 对应选项卡的 key | String | - 25 | tab | 选项卡头显示文字 | ReactNode | - 26 | 27 | ### Login.UserName 28 | 29 | 参数 | 说明 | 类型 | 默认值 30 | ----|------|-----|------ 31 | name | 控件标记,提交数据中同样以此为 key | String | - 32 | rules | 校验规则,同 Form getFieldDecorator(id, options) 中 [option.rules 的规则](getFieldDecorator(id, options)) | object[] | - 33 | 34 | 除上述属性以外,Login.UserName 还支持 antd.Input 的所有属性,并且自带默认的基础配置,包括 `placeholder` `size` `prefix` 等,这些基础配置均可被覆盖。 35 | 36 | ### Login.Password、Login.Mobile 同 Login.UserName 37 | 38 | ### Login.Captcha 39 | 40 | 参数 | 说明 | 类型 | 默认值 41 | ----|------|-----|------ 42 | onGetCaptcha | 点击获取校验码的回调 | () => void | - 43 | 44 | 除上述属性以外,Login.Captcha 支持的属性与 Login.UserName 相同。 45 | 46 | ### Login.Submit 47 | 48 | 支持 antd.Button 的所有属性。 49 | -------------------------------------------------------------------------------- /src/models/form.js: -------------------------------------------------------------------------------- 1 | import { routerRedux } from 'dva/router' 2 | import { message } from 'antd' 3 | import { fakeSubmitForm } from '../services/api' 4 | 5 | export default { 6 | namespace: 'form', 7 | 8 | state: { 9 | step: { 10 | payAccount: 'ant-design@alipay.com', 11 | receiverAccount: 'test@example.com', 12 | receiverName: 'Alex', 13 | amount: '500', 14 | }, 15 | }, 16 | 17 | effects: { 18 | *submitRegularForm({ payload }, { call }) { 19 | yield call(fakeSubmitForm, payload) 20 | message.success('提交成功') 21 | }, 22 | *submitStepForm({ payload }, { call, put }) { 23 | yield call(fakeSubmitForm, payload) 24 | yield put({ 25 | type: 'saveStepFormData', 26 | payload, 27 | }) 28 | yield put(routerRedux.push('/form/step-form/result')) 29 | }, 30 | *submitAdvancedForm({ payload }, { call }) { 31 | yield call(fakeSubmitForm, payload) 32 | message.success('提交成功') 33 | }, 34 | }, 35 | 36 | reducers: { 37 | saveStepFormData(state, { payload }) { 38 | return { 39 | ...state, 40 | step: { 41 | ...state.step, 42 | ...payload, 43 | }, 44 | } 45 | }, 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | min-height: 100%; 7 | background: #f0f2f5; 8 | } 9 | 10 | .content { 11 | padding: 32px 0; 12 | flex: 1; 13 | } 14 | 15 | @media (min-width: @screen-md-min) { 16 | .container { 17 | background-image: url('/exImages/TVYTbAXWheQpRcWDaDMu.svg'); 18 | background-repeat: no-repeat; 19 | background-position: center 110px; 20 | background-size: 100%; 21 | } 22 | 23 | .content { 24 | padding: 112px 0 24px 0; 25 | } 26 | } 27 | 28 | .top { 29 | text-align: center; 30 | } 31 | 32 | .header { 33 | height: 44px; 34 | line-height: 44px; 35 | a { 36 | text-decoration: none; 37 | } 38 | } 39 | 40 | .logo { 41 | height: 44px; 42 | vertical-align: top; 43 | margin-right: 16px; 44 | } 45 | 46 | .title { 47 | font-size: 33px; 48 | color: @heading-color; 49 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 50 | font-weight: 600; 51 | position: relative; 52 | top: 2px; 53 | } 54 | 55 | .desc { 56 | font-size: @font-size-base; 57 | color: @text-color-secondary; 58 | margin-top: 12px; 59 | margin-bottom: 40px; 60 | } 61 | -------------------------------------------------------------------------------- /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() 9 | }) 10 | 11 | beforeEach(async () => { 12 | page = await browser.newPage() 13 | await page.goto('http://localhost:8000/#/user/login') 14 | await page.evaluate(() => window.localStorage.setItem('antd-pro-authority', 'guest')) 15 | }) 16 | 17 | afterEach(() => page.close()) 18 | 19 | it('should login with failure', async () => { 20 | await page.type('#userName', 'mockuser') 21 | await page.type('#password', 'wrong_password') 22 | await page.click('button[type="submit"]') 23 | await page.waitForSelector('.ant-alert-error') // should display error 24 | }) 25 | 26 | it('should login successfully', async () => { 27 | await page.type('#userName', 'admin') 28 | await page.type('#password', '888888') 29 | await page.click('button[type="submit"]') 30 | await page.waitForSelector('.ant-layout-sider h1') // should display error 31 | const text = await page.evaluate(() => document.body.innerHTML) 32 | expect(text).toContain('

Ant Design Pro

') 33 | }) 34 | 35 | afterAll(() => browser.close()) 36 | }) 37 | -------------------------------------------------------------------------------- /src/pages/List/Projects.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .coverCardList { 5 | margin-bottom: -24px; 6 | 7 | .card { 8 | :global { 9 | .ant-card-meta-title { 10 | margin-bottom: 4px; 11 | & > a { 12 | color: @heading-color; 13 | display: inline-block; 14 | max-width: 100%; 15 | } 16 | } 17 | .ant-card-meta-description { 18 | height: 44px; 19 | line-height: 22px; 20 | overflow: hidden; 21 | } 22 | } 23 | 24 | &:hover { 25 | :global { 26 | .ant-card-meta-title > a { 27 | color: @primary-color; 28 | } 29 | } 30 | } 31 | } 32 | 33 | .cardItemContent { 34 | display: flex; 35 | margin-top: 16px; 36 | margin-bottom: -4px; 37 | line-height: 20px; 38 | height: 20px; 39 | & > span { 40 | color: @text-color-secondary; 41 | flex: 1; 42 | font-size: 12px; 43 | } 44 | .avatarList { 45 | flex: 0 1 auto; 46 | } 47 | } 48 | .cardList { 49 | margin-top: 24px; 50 | } 51 | 52 | :global { 53 | .ant-list .ant-list-item-content-single { 54 | max-width: 100%; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/EditableLinkGroup/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, createElement } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Button } from 'antd' 4 | import styles from './index.less' 5 | 6 | // TODO: 添加逻辑 7 | 8 | class EditableLinkGroup extends PureComponent { 9 | static defaultProps = { 10 | links: [], 11 | onAdd: () => {}, 12 | linkElement: 'a', 13 | } 14 | 15 | static propTypes = { 16 | links: PropTypes.array, 17 | onAdd: PropTypes.func, 18 | linkElement: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), 19 | } 20 | 21 | render() { 22 | const { links, linkElement, onAdd } = this.props 23 | return ( 24 |
25 | {links.map(link => 26 | createElement( 27 | linkElement, 28 | { 29 | key: `linkGroup-item-${link.id || link.title}`, 30 | to: link.href, 31 | href: link.href, 32 | }, 33 | link.title 34 | ) 35 | )} 36 | { 37 | 40 | } 41 |
42 | ) 43 | } 44 | } 45 | 46 | export default EditableLinkGroup 47 | -------------------------------------------------------------------------------- /src/pages/Exception/triggerException.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Button, Spin, Card } from 'antd' 3 | import { connect } from 'dva' 4 | import styles from './style.less' 5 | 6 | @connect(state => ({ 7 | isloading: state.error.isloading, 8 | })) 9 | export default class TriggerException extends PureComponent { 10 | state = { 11 | isloading: false, 12 | } 13 | triggerError = code => { 14 | this.setState({ 15 | isloading: true, 16 | }) 17 | this.props.dispatch({ 18 | type: 'error/query', 19 | payload: { 20 | code, 21 | }, 22 | }) 23 | } 24 | render() { 25 | return ( 26 | 27 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/EditableItem/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { Input, Icon } from 'antd' 3 | import styles from './index.less' 4 | 5 | export default class EditableItem extends PureComponent { 6 | state = { 7 | value: this.props.value, 8 | editable: false, 9 | } 10 | handleChange = e => { 11 | const { value } = e.target 12 | this.setState({ value }) 13 | } 14 | check = () => { 15 | this.setState({ editable: false }) 16 | if (this.props.onChange) { 17 | this.props.onChange(this.state.value) 18 | } 19 | } 20 | edit = () => { 21 | this.setState({ editable: true }) 22 | } 23 | render() { 24 | const { value, editable } = this.state 25 | return ( 26 |
27 | {editable ? ( 28 |
29 | 30 | 31 |
32 | ) : ( 33 |
34 | {value || ' '} 35 | 36 |
37 | )} 38 |
39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/pages/List/Articles.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .listContent { 5 | .description { 6 | line-height: 22px; 7 | max-width: 720px; 8 | } 9 | .extra { 10 | color: @text-color-secondary; 11 | margin-top: 16px; 12 | line-height: 22px; 13 | & > :global(.ant-avatar) { 14 | vertical-align: top; 15 | margin-right: 8px; 16 | width: 20px; 17 | height: 20px; 18 | position: relative; 19 | top: 1px; 20 | } 21 | & > em { 22 | color: @disabled-color; 23 | font-style: normal; 24 | margin-left: 16px; 25 | } 26 | } 27 | } 28 | a.listItemMetaTitle { 29 | color: @heading-color; 30 | } 31 | .listItemExtra { 32 | width: 272px; 33 | height: 1px; 34 | } 35 | .selfTrigger { 36 | margin-left: 12px; 37 | } 38 | 39 | @media screen and (max-width: @screen-xs) { 40 | .selfTrigger { 41 | display: block; 42 | margin-left: 0; 43 | } 44 | .listContent { 45 | .extra { 46 | & > em { 47 | display: block; 48 | margin-left: 0; 49 | margin-top: 8px; 50 | } 51 | } 52 | } 53 | } 54 | @media screen and (max-width: @screen-md) { 55 | .selfTrigger { 56 | display: block; 57 | margin-left: 0; 58 | } 59 | } 60 | @media screen and (max-width: @screen-lg) { 61 | .listItemExtra { 62 | width: 0; 63 | height: 1px; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/pages/Result/Error.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import { Button, Icon, Card } from 'antd' 3 | import Result from 'ant-design-pro/lib/Result' 4 | import PageHeaderLayout from '../../layouts/PageHeaderLayout' 5 | 6 | const extra = ( 7 | 8 |
16 | 您提交的内容有如下错误: 17 |
18 |
19 | 您的账户已被冻结 20 | 21 | 立即解冻 22 | 23 |
24 |
25 | 您的账户还不具备申请资格 26 | 27 | 立即升级 28 | 29 |
30 |
31 | ) 32 | 33 | const actions = 34 | 35 | export default () => ( 36 | 37 | 38 | 46 | 47 | 48 | ) 49 | -------------------------------------------------------------------------------- /src/models/chart.js: -------------------------------------------------------------------------------- 1 | import { fakeChartData } from '../services/api' 2 | 3 | export default { 4 | namespace: 'chart', 5 | 6 | state: { 7 | visitData: [], 8 | visitData2: [], 9 | salesData: [], 10 | searchData: [], 11 | offlineData: [], 12 | offlineChartData: [], 13 | salesTypeData: [], 14 | salesTypeDataOnline: [], 15 | salesTypeDataOffline: [], 16 | radarData: [], 17 | loading: false, 18 | }, 19 | 20 | effects: { 21 | *fetch(_, { call, put }) { 22 | const response = yield call(fakeChartData) 23 | yield put({ 24 | type: 'save', 25 | payload: response, 26 | }) 27 | }, 28 | *fetchSalesData(_, { call, put }) { 29 | const response = yield call(fakeChartData) 30 | yield put({ 31 | type: 'save', 32 | payload: { 33 | salesData: response.salesData, 34 | }, 35 | }) 36 | }, 37 | }, 38 | 39 | reducers: { 40 | save(state, { payload }) { 41 | return { 42 | ...state, 43 | ...payload, 44 | } 45 | }, 46 | clear() { 47 | return { 48 | visitData: [], 49 | visitData2: [], 50 | salesData: [], 51 | searchData: [], 52 | offlineData: [], 53 | offlineChartData: [], 54 | salesTypeData: [], 55 | salesTypeDataOnline: [], 56 | salesTypeDataOffline: [], 57 | radarData: [], 58 | } 59 | }, 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/Forms/StepForm/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .stepForm { 4 | margin: 40px auto 0; 5 | max-width: 500px; 6 | } 7 | 8 | .stepFormText { 9 | margin-bottom: 24px; 10 | :global { 11 | .ant-form-item-label, 12 | .ant-form-item-control { 13 | line-height: 22px; 14 | } 15 | } 16 | } 17 | 18 | .result { 19 | margin: 0 auto; 20 | max-width: 560px; 21 | padding: 24px 0 8px; 22 | } 23 | 24 | .desc { 25 | padding: 0 56px; 26 | color: @text-color-secondary; 27 | h3 { 28 | font-size: 16px; 29 | margin: 0 0 12px 0; 30 | color: @text-color-secondary; 31 | line-height: 32px; 32 | } 33 | h4 { 34 | margin: 0 0 4px 0; 35 | color: @text-color-secondary; 36 | font-size: 14px; 37 | line-height: 22px; 38 | } 39 | p { 40 | margin-top: 0; 41 | margin-bottom: 12px; 42 | line-height: 22px; 43 | } 44 | } 45 | 46 | @media screen and (max-width: @screen-md) { 47 | .desc { 48 | padding: 0; 49 | } 50 | } 51 | 52 | .information { 53 | line-height: 22px; 54 | :global { 55 | .ant-row:not(:last-child) { 56 | margin-bottom: 24px; 57 | } 58 | } 59 | .label { 60 | color: @heading-color; 61 | text-align: right; 62 | padding-right: 8px; 63 | } 64 | } 65 | 66 | .money { 67 | font-family: 'Helvetica Neue', sans-serif; 68 | font-weight: 500; 69 | font-size: 20px; 70 | line-height: 14px; 71 | } 72 | 73 | .uppercase { 74 | font-size: 12px; 75 | } 76 | -------------------------------------------------------------------------------- /src/components/StandardFormRow/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .standardFormRow { 4 | border-bottom: 1px dashed @border-color-split; 5 | padding-bottom: 16px; 6 | margin-bottom: 16px; 7 | display: flex; 8 | :global { 9 | .ant-form-item { 10 | margin-right: 24px; 11 | } 12 | .ant-form-item-label label { 13 | color: @text-color; 14 | margin-right: 0; 15 | } 16 | .ant-form-item-label, 17 | .ant-form-item-control { 18 | padding: 0; 19 | line-height: 32px; 20 | } 21 | } 22 | .label { 23 | color: @heading-color; 24 | font-size: @font-size-base; 25 | margin-right: 24px; 26 | flex: 0 0 auto; 27 | text-align: right; 28 | & > span { 29 | display: inline-block; 30 | height: 32px; 31 | line-height: 32px; 32 | &:after { 33 | content: ':'; 34 | } 35 | } 36 | } 37 | .content { 38 | flex: 1 1 0; 39 | :global { 40 | .ant-form-item:last-child { 41 | margin-right: 0; 42 | } 43 | } 44 | } 45 | } 46 | 47 | .standardFormRowLast { 48 | border: none; 49 | padding-bottom: 0; 50 | margin-bottom: 0; 51 | } 52 | 53 | .standardFormRowBlock { 54 | :global { 55 | .ant-form-item, 56 | div.ant-form-item-control-wrapper { 57 | display: block; 58 | } 59 | } 60 | } 61 | 62 | .standardFormRowGrid { 63 | :global { 64 | .ant-form-item, 65 | div.ant-form-item-control-wrapper { 66 | display: block; 67 | } 68 | .ant-form-item-label { 69 | float: left; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { routerRedux, Route, Switch } from 'dva/router' 3 | import { LocaleProvider, Spin } from 'antd' 4 | import zhCN from 'antd/lib/locale-provider/zh_CN' 5 | import dynamic from 'dva/dynamic' 6 | import { PersistGate } from 'redux-persist/lib/integration/react' 7 | import { getRouterData } from './common/router' 8 | import Authorized from './utils/Authorized' 9 | import styles from './index.less' 10 | import { createPersistorIfNecessary } from './index' 11 | 12 | const { ConnectedRouter } = routerRedux 13 | const { AuthorizedRoute } = Authorized 14 | 15 | const Loading = 16 | 17 | dynamic.setDefaultLoadingComponent(() => Loading) 18 | 19 | function RouterConfig({ history, app }) { 20 | const routerData = getRouterData(app) 21 | const UserLayout = routerData['/user'].component 22 | const BasicLayout = routerData['/'].component 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | } 32 | authority={['admin', 'user']} 33 | redirectPath="/user/login" 34 | /> 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default RouterConfig 43 | -------------------------------------------------------------------------------- /src/components/Login/map.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Input, Icon } from 'antd' 3 | import styles from './index.less' 4 | 5 | const map = { 6 | UserName: { 7 | component: Input, 8 | props: { 9 | size: 'large', 10 | prefix: , 11 | placeholder: 'admin', 12 | }, 13 | rules: [ 14 | { 15 | required: true, 16 | message: 'Please enter username!', 17 | }, 18 | ], 19 | }, 20 | Password: { 21 | component: Input, 22 | props: { 23 | size: 'large', 24 | prefix: , 25 | type: 'password', 26 | placeholder: '888888', 27 | }, 28 | rules: [ 29 | { 30 | required: true, 31 | message: 'Please enter password!', 32 | }, 33 | ], 34 | }, 35 | Mobile: { 36 | component: Input, 37 | props: { 38 | size: 'large', 39 | prefix: , 40 | placeholder: 'mobile number', 41 | }, 42 | rules: [ 43 | { 44 | required: true, 45 | message: 'Please enter mobile number!', 46 | }, 47 | { 48 | pattern: /^1\d{10}$/, 49 | message: 'Wrong mobile number format!', 50 | }, 51 | ], 52 | }, 53 | Captcha: { 54 | component: Input, 55 | props: { 56 | size: 'large', 57 | prefix: , 58 | placeholder: 'captcha', 59 | }, 60 | rules: [ 61 | { 62 | required: true, 63 | message: 'Please enter Captcha!', 64 | }, 65 | ], 66 | }, 67 | } 68 | 69 | export default map 70 | -------------------------------------------------------------------------------- /src/components/Login/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Login 3 | cols: 1 4 | order: 15 5 | --- 6 | 7 | Support multiple common ways of login with built-in controls. You can choose your own combinations and use with your custom controls. 8 | 9 | ## API 10 | 11 | ### Login 12 | 13 | Property | Description | Type | Default 14 | ----|------|-----|------ 15 | defaultActiveKey | default key to activate the tab panel | String | - 16 | onTabChange | callback on changing tabs | (key) => void | - 17 | onSubmit | callback on submit | (err, values) => void | - 18 | 19 | ### Login.Tab 20 | 21 | Property | Description | Type | Default 22 | ----|------|-----|------ 23 | key | key of the tab | String | - 24 | tab | displayed text of the tab | ReactNode | - 25 | 26 | ### Login.UserName 27 | 28 | Property | Description | Type | Default 29 | ----|------|-----|------ 30 | name | name of the control, also the key of the submitted data | String | - 31 | rules | validation rules, same with [option.rules](getFieldDecorator(id, options)) in Form getFieldDecorator(id, options) | object[] | - 32 | 33 | Apart from the above properties, Login.Username also support all properties of antd.Input, together with the default values of basic settings, such as _placeholder_, _size_ and _prefix_. All of these default values can be over-written. 34 | 35 | ### Login.Password, Login.Mobile are the same as Login.UserName 36 | 37 | ### Login.Captcha 38 | 39 | Property | Description | Type | Default 40 | ----|------|-----|------ 41 | onGetCaptcha | callback on getting a new Captcha | () => void | - 42 | 43 | Apart from the above properties, _Login.Captcha_ support the same properties with _Login.UserName_. 44 | 45 | ### Login.Submit 46 | 47 | Support all properties of _antd.Button_. -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'ant-design-pro/dist/ant-design-pro.css' 2 | import '@babel/polyfill' 3 | import 'url-polyfill' 4 | import dva from 'dva' 5 | import { persistReducer, persistStore } from 'redux-persist' 6 | import storage from 'redux-persist/lib/storage' 7 | 8 | import createHistory from 'history/createHashHistory' 9 | // user BrowserHistory 10 | // import createHistory from 'history/createBrowserHistory'; 11 | import createLoading from 'dva-loading' 12 | import 'moment/locale/zh-cn' 13 | 14 | import './rollbar' 15 | import onError from './error' 16 | 17 | import './index.less' 18 | 19 | const persistConfig = { 20 | key: 'root', 21 | storage: storage, 22 | whitelist: ['global', 'user'], 23 | } 24 | 25 | let $persistor 26 | export function createPersistorIfNecessary(store) { 27 | if (!$persistor && store) { 28 | $persistor = persistStore(store) 29 | const rootReducer = persistReducer(persistConfig, state => state) 30 | store.replaceReducer(rootReducer) 31 | $persistor.persist() 32 | } 33 | return $persistor 34 | } 35 | 36 | // 1. Initialize 37 | const app = dva({ 38 | onReducer: reducer => { 39 | if (createPersistorIfNecessary(app._store)) { 40 | const newReducer = persistReducer(persistConfig, reducer) 41 | setTimeout(() => $persistor && $persistor.persist(), 0) 42 | return newReducer 43 | } else { 44 | return reducer 45 | } 46 | }, 47 | history: createHistory(), 48 | onError, 49 | }) 50 | 51 | // 2. Plugins 52 | app.use(createLoading()) 53 | 54 | // 3. Register global model 55 | app.model(require('./models/global').default) 56 | 57 | // 4. Router 58 | app.router(require('./router').default) 59 | 60 | // 5. Start 61 | app.start('#root') 62 | 63 | export default app._store // eslint-disable-line 64 | -------------------------------------------------------------------------------- /src/models/global.js: -------------------------------------------------------------------------------- 1 | import { queryNotices } from '../services/api' 2 | 3 | export default { 4 | namespace: 'global', 5 | 6 | state: { 7 | collapsed: false, 8 | notices: [], 9 | }, 10 | 11 | effects: { 12 | *fetchNotices(_, { call, put }) { 13 | const data = yield call(queryNotices) 14 | yield put({ 15 | type: 'saveNotices', 16 | payload: data, 17 | }) 18 | yield put({ 19 | type: 'user/changeNotifyCount', 20 | payload: data.length, 21 | }) 22 | }, 23 | *clearNotices({ payload }, { put, select }) { 24 | yield put({ 25 | type: 'saveClearedNotices', 26 | payload, 27 | }) 28 | const count = yield select(state => state.global.notices.length) 29 | yield put({ 30 | type: 'user/changeNotifyCount', 31 | payload: count, 32 | }) 33 | }, 34 | }, 35 | 36 | reducers: { 37 | changeLayoutCollapsed(state, { payload }) { 38 | return { 39 | ...state, 40 | collapsed: payload, 41 | } 42 | }, 43 | saveNotices(state, { payload }) { 44 | return { 45 | ...state, 46 | notices: payload, 47 | } 48 | }, 49 | saveClearedNotices(state, { payload }) { 50 | return { 51 | ...state, 52 | notices: state.notices.filter(item => item.type !== payload), 53 | } 54 | }, 55 | }, 56 | 57 | subscriptions: { 58 | setup({ history }) { 59 | // Subscribe history(url) change, trigger `load` action if pathname is `/` 60 | return history.listen(({ pathname, search }) => { 61 | if (typeof window.ga !== 'undefined') { 62 | window.ga('send', 'pageview', pathname + search) 63 | } 64 | }) 65 | }, 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /src/models/login.js: -------------------------------------------------------------------------------- 1 | import { routerRedux } from 'dva/router' 2 | import { fakeAccountLogin } from '../services/api' 3 | import { setAuthority } from '../utils/authority' 4 | import { reloadAuthorized } from '../utils/Authorized' 5 | 6 | export default { 7 | namespace: 'login', 8 | 9 | state: { 10 | status: undefined, 11 | }, 12 | 13 | effects: { 14 | *login({ payload }, { call, put }) { 15 | const response = yield call(fakeAccountLogin, payload) 16 | yield put({ 17 | type: 'changeLoginStatus', 18 | payload: response, 19 | }) 20 | // Login successfully 21 | if (response.status === 'ok') { 22 | reloadAuthorized() 23 | yield put(routerRedux.push('/')) 24 | } 25 | }, 26 | *logout(_, { put, select }) { 27 | try { 28 | // get location pathname 29 | const urlParams = new URL(window.location.href) 30 | const pathname = yield select(state => state.routing.location.pathname) 31 | // add the parameters in the url 32 | urlParams.searchParams.set('redirect', pathname) 33 | window.history.replaceState(null, 'login', urlParams.href) 34 | } finally { 35 | yield put({ 36 | type: 'changeLoginStatus', 37 | payload: { 38 | status: false, 39 | currentAuthority: 'guest', 40 | }, 41 | }) 42 | reloadAuthorized() 43 | yield put(routerRedux.push('/user/login')) 44 | } 45 | }, 46 | }, 47 | 48 | reducers: { 49 | changeLoginStatus(state, { payload }) { 50 | setAuthority(payload.currentAuthority) 51 | return { 52 | ...state, 53 | status: payload.status, 54 | type: payload.type, 55 | } 56 | }, 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /src/common/menu.js: -------------------------------------------------------------------------------- 1 | import memoize from 'lodash/memoize' 2 | import isArray from 'lodash/isArray' 3 | import nav from './nav' 4 | 5 | function innerFormatter(navDatas, parentPath = '/', parentAuth) { 6 | return navDatas.reduce((acc, navData) => { 7 | const { 8 | name, 9 | path, 10 | children, 11 | auth, 12 | menu: isMenu = true, 13 | models, 14 | page, 15 | ...restProps 16 | } = navData.props 17 | const fullPath = `${parentPath}${path || ''}`.replace(/\/{2,}/g, '/') 18 | const childDatas = children 19 | ? innerFormatter( 20 | isArray(children) ? children : isArray(children.type) ? children.type : [children], 21 | fullPath, 22 | auth 23 | ) 24 | : null 25 | const ret = [] 26 | if (isMenu) { 27 | const menuNode = { 28 | ...restProps, 29 | name, 30 | path: fullPath, 31 | authority: auth || parentAuth, 32 | } 33 | if (children) { 34 | menuNode.children = childDatas 35 | } 36 | ret.push(menuNode) 37 | } else if (children) { 38 | ret.push(...childDatas) 39 | } 40 | return [...acc, ...ret] 41 | }, []) 42 | } 43 | 44 | // function formatter(data, parentPath = '', parentAuthority) { 45 | // return data.map((item) => { 46 | // const result = { 47 | // ...item, 48 | // path: `${parentPath}${item.path}`, 49 | // authority: item.authority || parentAuthority, 50 | // } 51 | // if (item.children) { 52 | // result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority) 53 | // } 54 | // return result 55 | // }) 56 | // } 57 | const formatter = memoize(innerFormatter) 58 | 59 | export const getMenuData = () => formatter(nav) 60 | -------------------------------------------------------------------------------- /src/pages/Forms/style.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .card { 4 | margin-bottom: 24px; 5 | } 6 | 7 | .heading { 8 | font-size: 14px; 9 | line-height: 22px; 10 | margin: 0 0 16px 0; 11 | } 12 | 13 | .steps:global(.ant-steps) { 14 | max-width: 750px; 15 | margin: 16px auto; 16 | } 17 | 18 | .errorIcon { 19 | cursor: pointer; 20 | color: @error-color; 21 | margin-right: 24px; 22 | i { 23 | margin-right: 4px; 24 | } 25 | } 26 | 27 | .errorPopover { 28 | :global { 29 | .ant-popover-inner-content { 30 | padding: 0; 31 | max-height: 290px; 32 | overflow: auto; 33 | min-width: 256px; 34 | } 35 | } 36 | } 37 | 38 | .errorListItem { 39 | list-style: none; 40 | border-bottom: 1px solid @border-color-split; 41 | padding: 8px 16px; 42 | cursor: pointer; 43 | transition: all 0.3s; 44 | &:hover { 45 | background: @primary-1; 46 | } 47 | &:last-child { 48 | border: 0; 49 | } 50 | .errorIcon { 51 | color: @error-color; 52 | float: left; 53 | margin-top: 4px; 54 | margin-right: 12px; 55 | padding-bottom: 22px; 56 | } 57 | .errorField { 58 | font-size: 12px; 59 | color: @text-color-secondary; 60 | margin-top: 2px; 61 | } 62 | } 63 | 64 | .editable { 65 | td { 66 | padding-top: 13px !important; 67 | padding-bottom: 12.5px !important; 68 | } 69 | } 70 | 71 | // custom footer for fixed footer toolbar 72 | .advancedForm + div { 73 | padding-bottom: 64px; 74 | } 75 | 76 | .advancedForm { 77 | :global { 78 | .ant-form .ant-row:last-child .ant-form-item { 79 | margin-bottom: 24px; 80 | } 81 | .ant-table td { 82 | transition: none !important; 83 | } 84 | } 85 | } 86 | 87 | .optional { 88 | color: @text-color-secondary; 89 | font-style: normal; 90 | } 91 | -------------------------------------------------------------------------------- /public/exImages/NbuDUAuBlIApFuDvWiND.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 17 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); 3 | .logo { 4 | height: 64px; 5 | position: relative; 6 | line-height: 64px; 7 | padding-left: (@menu-collapsed-width - 32px) / 2; 8 | transition: all 0.3s; 9 | background: #002140; 10 | overflow: hidden; 11 | img { 12 | display: inline-block; 13 | vertical-align: middle; 14 | height: 32px; 15 | } 16 | h1 { 17 | color: white; 18 | display: inline-block; 19 | vertical-align: middle; 20 | font-size: 20px; 21 | margin: 0 0 0 12px; 22 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 23 | font-weight: 600; 24 | } 25 | } 26 | 27 | .sider { 28 | min-height: 100vh; 29 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); 30 | position: relative; 31 | z-index: 10; 32 | &.ligth { 33 | background-color: white; 34 | .logo { 35 | background: white; 36 | h1 { 37 | color: #002140; 38 | } 39 | } 40 | } 41 | } 42 | 43 | .icon { 44 | width: 14px; 45 | margin-right: 10px; 46 | } 47 | 48 | :global { 49 | .drawer .drawer-content { 50 | background: #001529; 51 | } 52 | .ant-menu-inline-collapsed { 53 | & > .ant-menu-item .sider-menu-item-img + span, 54 | & 55 | > .ant-menu-item-group 56 | > .ant-menu-item-group-list 57 | > .ant-menu-item 58 | .sider-menu-item-img 59 | + span, 60 | & > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span { 61 | max-width: 0; 62 | display: inline-block; 63 | opacity: 0; 64 | } 65 | } 66 | .ant-menu-item .sider-menu-item-img + span, 67 | .ant-menu-submenu-title .sider-menu-item-img + span { 68 | transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out; 69 | opacity: 1; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /mock/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export const imgMap = { 4 | user: '/exImages/UjusLxePxWGkttaqqmUI.png', 5 | a: '/exImages/ZrkcSjizAKNWwJTwcadT.png', 6 | b: '/exImages/KYlwHMeomKQbhJDRUVvt.png', 7 | c: '/exImages/gabvleTstEvzkbQRfjxu.png', 8 | d: '/exImages/jvpNzacxUYLlNsHTtrAD.png', 9 | } 10 | 11 | // refers: https://www.sitepoint.com/get-url-parameters-with-javascript/ 12 | export function getUrlParams(url) { 13 | const d = decodeURIComponent 14 | let queryString = url ? url.split('?')[1] : window.location.search.slice(1) 15 | const obj = {} 16 | if (queryString) { 17 | queryString = queryString.split('#')[0]; // eslint-disable-line 18 | const arr = queryString.split('&') 19 | for (let i = 0; i < arr.length; i += 1) { 20 | const a = arr[i].split('=') 21 | let paramNum 22 | const paramName = a[0].replace(/\[\d*\]/, (v) => { 23 | paramNum = v.slice(1, -1) 24 | return '' 25 | }) 26 | const paramValue = typeof (a[1]) === 'undefined' ? true : a[1] 27 | if (obj[paramName]) { 28 | if (typeof obj[paramName] === 'string') { 29 | obj[paramName] = d([obj[paramName]]) 30 | } 31 | if (typeof paramNum === 'undefined') { 32 | obj[paramName].push(d(paramValue)) 33 | } else { 34 | obj[paramName][paramNum] = d(paramValue) 35 | } 36 | } else { 37 | obj[paramName] = d(paramValue) 38 | } 39 | } 40 | } 41 | return obj 42 | } 43 | 44 | const isProxy = '__isProxy__' 45 | 46 | const proxyWrapper = function (target) { 47 | return { 48 | [isProxy]: true, 49 | target, 50 | } 51 | } 52 | 53 | /** 54 | * 支持webpack.proxy 55 | */ 56 | export const proxy = target => target ? proxyWrapper(target) : proxy 57 | 58 | proxy.is = obj => obj === proxy || obj[isProxy] 59 | 60 | export default { 61 | getUrlParams, 62 | imgMap, 63 | proxy, 64 | } 65 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | import { stringify } from 'qs' 2 | import request from '../utils/request' 3 | 4 | export async function queryProjectNotice() { 5 | return request('/api/project/notice') 6 | } 7 | 8 | export async function queryActivities() { 9 | return request('/api/activities') 10 | } 11 | 12 | export async function queryRule(params) { 13 | return request(`/api/rule?${stringify(params)}`) 14 | } 15 | 16 | export async function removeRule(params) { 17 | return request('/api/rule', { 18 | method: 'POST', 19 | body: { 20 | ...params, 21 | method: 'delete', 22 | }, 23 | }) 24 | } 25 | 26 | export async function addRule(params) { 27 | return request('/api/rule', { 28 | method: 'POST', 29 | body: { 30 | ...params, 31 | method: 'post', 32 | }, 33 | }) 34 | } 35 | 36 | export async function fakeSubmitForm(params) { 37 | return request('/api/forms', { 38 | method: 'POST', 39 | body: params, 40 | }) 41 | } 42 | 43 | export async function fakeChartData() { 44 | return request('/api/fake_chart_data') 45 | } 46 | 47 | export async function queryTags() { 48 | return request('/api/tags') 49 | } 50 | 51 | export async function queryBasicProfile() { 52 | return request('/api/profile/basic') 53 | } 54 | 55 | export async function queryAdvancedProfile() { 56 | return request('/api/profile/advanced') 57 | } 58 | 59 | export async function queryFakeList(params) { 60 | return request(`/api/fake_list?${stringify(params)}`) 61 | } 62 | 63 | export async function fakeAccountLogin(params) { 64 | return request('/api/login/account', { 65 | method: 'POST', 66 | body: params, 67 | }) 68 | } 69 | 70 | export async function fakeRegister(params) { 71 | return request('/api/register', { 72 | method: 'POST', 73 | body: params, 74 | }) 75 | } 76 | 77 | export async function queryNotices() { 78 | return request('/api/notices') 79 | } 80 | -------------------------------------------------------------------------------- /src/pages/Forms/StepForm/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react' 2 | import { Route, Redirect, Switch } from 'dva/router' 3 | import { Card, Steps } from 'antd' 4 | import PageHeaderLayout from '../../../layouts/PageHeaderLayout' 5 | import NotFound from '../../Exception/404' 6 | import { getRoutes } from '../../../utils/utils' 7 | import styles from '../style.less' 8 | 9 | const { Step } = Steps 10 | 11 | export default class StepForm extends PureComponent { 12 | getCurrentStep() { 13 | const { location } = this.props 14 | const { pathname } = location 15 | const pathList = pathname.split('/') 16 | switch (pathList[pathList.length - 1]) { 17 | case 'info': 18 | return 0 19 | case 'confirm': 20 | return 1 21 | case 'result': 22 | return 2 23 | default: 24 | return 0 25 | } 26 | } 27 | render() { 28 | const { match, routerData } = this.props 29 | return ( 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {getRoutes(match.path, routerData).map(item => ( 43 | 49 | ))} 50 | 51 | 52 | 53 | 54 | 55 | 56 | ) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/pages/Forms/StepForm/Step3.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import { connect } from 'dva' 3 | import { Button, Row, Col } from 'antd' 4 | import { routerRedux } from 'dva/router' 5 | import Result from 'ant-design-pro/lib/Result' 6 | import styles from './style.less' 7 | 8 | class Step3 extends React.PureComponent { 9 | render() { 10 | const { dispatch, data } = this.props 11 | const onFinish = () => { 12 | dispatch(routerRedux.push('/form/step-form')) 13 | } 14 | const information = ( 15 |
16 | 17 | 18 | 付款账户: 19 | 20 | {data.payAccount} 21 | 22 | 23 | 24 | 收款账户: 25 | 26 | {data.receiverAccount} 27 | 28 | 29 | 30 | 收款人姓名: 31 | 32 | {data.receiverName} 33 | 34 | 35 | 36 | 转账金额: 37 | 38 | 39 | {data.amount} 元 40 | 41 | 42 |
43 | ) 44 | const actions = ( 45 | 46 | 49 | 50 | 51 | ) 52 | return ( 53 | 61 | ) 62 | } 63 | } 64 | 65 | export default connect(({ form }) => ({ 66 | data: form.step, 67 | }))(Step3) 68 | -------------------------------------------------------------------------------- /.webpackrc.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { proxy } from './mock/utils' 4 | import isString from 'lodash/isString' 5 | 6 | const methodFilter = method => (pathname, req) => { 7 | return method ? req.method === method : true; 8 | } 9 | 10 | const parseMocks = (mocks) => { 11 | if (!mocks) { 12 | return {} 13 | } 14 | return mocks.reduce((acc, [key, value]) => { 15 | let [method, uri] = key.split(/\s+/) 16 | if (!uri) { 17 | uri = method 18 | method = null 19 | } 20 | const { target } = value 21 | acc[uri] = isString(target) 22 | ? { 23 | target, 24 | changeOrigin: true, 25 | } 26 | : { 27 | changeOrigin: true, 28 | ...target, 29 | } 30 | return acc 31 | }, {}) 32 | } 33 | 34 | const proxyMock = fs.readdirSync(path.join(__dirname + '/mock')) 35 | .filter(file => file !== 'utils.js' && file.endsWith('.js')) 36 | .reduce((acc, file) => { 37 | const module = require('./mock/' + file) 38 | let mocks = (module.default || module) 39 | for (const key in mocks) { 40 | if (proxy.is(mocks[key])) { 41 | acc.push([key, mocks[key]]) 42 | } 43 | } 44 | return acc 45 | }, []) 46 | 47 | const proxyObject = parseMocks(proxyMock) 48 | // console.log('所有代理接口:') 49 | // console.log(Object.keys(proxyMock)) 50 | 51 | export default { 52 | proxy: proxyObject, 53 | entry: 'src/index.js', 54 | extraBabelPlugins: [ 55 | ['module-resolver', { 56 | 'alias': { 57 | 'src': './src' 58 | } 59 | }], 60 | 'transform-decorators-legacy', 61 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }] 62 | ], 63 | env: { 64 | development: { 65 | extraBabelPlugins: [ 66 | 'dva-hmr' 67 | ], 68 | devtool: 'cheap-module-eval-source-map' 69 | }, 70 | // production: { 71 | // devtool: 'source-map' 72 | // } 73 | }, 74 | ignoreMomentLocale: true, 75 | theme: './src/theme.js', 76 | html: { 77 | template: './src/index.ejs' 78 | }, 79 | publicPath: '/', 80 | disableDynamicImport: false, 81 | hash: true 82 | } 83 | -------------------------------------------------------------------------------- /.roadhogrc.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { proxy } from './mock/utils' 4 | import isString from 'lodash/isString' 5 | import httpProxy from 'http-proxy-middleware' 6 | 7 | const methodFilter = method => (pathname, req) => { 8 | return method ? req.method === method : true; 9 | }; 10 | 11 | const parseMocks = (mocks) => { 12 | if (!mocks) { 13 | return {} 14 | } 15 | return mocks.reduce((acc, [key, value]) => { 16 | let [method, uri] = key.split(/\s+/) 17 | if (!uri) { 18 | uri = method 19 | method = null 20 | } 21 | const { target } = value 22 | acc[uri] = isString(target) 23 | ? { 24 | target, 25 | changeOrigin: true, 26 | } 27 | : { 28 | changeOrigin: true, 29 | ...target, 30 | } 31 | return acc 32 | }, {}) 33 | } 34 | const proxyMock = fs.readdirSync(path.join(__dirname + '/mock')) 35 | .filter(file => file !== 'utils.js' && file.endsWith('.js')) 36 | .reduce((acc, file) => { 37 | const module = require('./mock/' + file) 38 | let mocks = (module.default || module) 39 | for (const key in mocks) { 40 | if (proxy.is(mocks[key])) { 41 | acc.push([key, mocks[key]]) 42 | } 43 | } 44 | return acc 45 | }, []) 46 | 47 | const proxyObject = parseMocks(proxyMock) 48 | export default { 49 | entry: 'src/index.js', 50 | proxy: proxyObject, 51 | extraBabelPlugins: [ 52 | ['module-resolver', { 53 | alias: { 54 | 'src': './src' 55 | } 56 | }], 57 | 'transform-runtime', 58 | 'transform-decorators-legacy', 59 | 'transform-class-properties', 60 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }] 61 | ], 62 | env: { 63 | development: { 64 | extraBabelPlugins: [ 65 | 'dva-hmr' 66 | ], 67 | devtool: 'cheap-module-source-map' 68 | }, 69 | production: { 70 | devtool: 'source-map' 71 | } 72 | }, 73 | ignoreMomentLocale: true, 74 | theme: './src/theme.js', 75 | html: { 76 | template: './src/index.ejs' 77 | }, 78 | publicPath: '/', 79 | disableDynamicImport: true, 80 | hash: true 81 | } 82 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import fetch from 'dva/fetch' 2 | 3 | const codeMessage = { 4 | 200: '服务器成功返回请求的数据。', 5 | 201: '新建或修改数据成功。', 6 | 202: '一个请求已经进入后台排队(异步任务)。', 7 | 204: '删除数据成功。', 8 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 9 | 401: '用户没有权限(令牌、用户名、密码错误)。', 10 | 403: '用户得到授权,但是访问是被禁止的。', 11 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 12 | 406: '请求的格式不可得。', 13 | 410: '请求的资源被永久删除,且不会再得到的。', 14 | 422: '当创建一个对象时,发生一个验证错误。', 15 | 500: '服务器发生错误,请检查服务器。', 16 | 502: '网关错误。', 17 | 503: '服务不可用,服务器暂时过载或维护。', 18 | 504: '网关超时。', 19 | } 20 | function checkStatus(response) { 21 | if (response.status >= 200 && response.status < 300) { 22 | return response 23 | } 24 | const errortext = codeMessage[response.status] || response.statusText 25 | const error = new Error(errortext) 26 | error.name = response.status 27 | error.response = response 28 | throw error 29 | } 30 | 31 | /** 32 | * Requests a URL, returning a promise. 33 | * 34 | * @param {string} url The URL we want to request 35 | * @param {object} [options] The options we want to pass to "fetch" 36 | * @return {object} An object containing either "data" or "err" 37 | */ 38 | export default function request(url, options) { 39 | const defaultOptions = { 40 | credentials: 'include', 41 | } 42 | const newOptions = { ...defaultOptions, ...options } 43 | if (newOptions.method === 'POST' || newOptions.method === 'PUT') { 44 | if (!(newOptions.body instanceof FormData)) { 45 | newOptions.headers = { 46 | Accept: 'application/json', 47 | 'Content-Type': 'application/json; charset=utf-8', 48 | ...newOptions.headers, 49 | } 50 | newOptions.body = JSON.stringify(newOptions.body) 51 | } else { 52 | // newOptions.body is FormData 53 | newOptions.headers = { 54 | Accept: 'application/json', 55 | 'Content-Type': 'multipart/form-data', 56 | ...newOptions.headers, 57 | } 58 | } 59 | } 60 | 61 | return fetch(url, newOptions) 62 | .then(checkStatus) 63 | .then(response => { 64 | if (newOptions.method === 'DELETE' || response.status === 204) { 65 | return response.text() 66 | } 67 | return response.json() 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /src/pages/List/List.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { routerRedux, Route, Switch, Redirect } from 'dva/router' 3 | import { connect } from 'dva' 4 | import { Input } from 'antd' 5 | import PageHeaderLayout from '../../layouts/PageHeaderLayout' 6 | import { getRoutes } from '../../utils/utils' 7 | 8 | @connect() 9 | export default class SearchList extends Component { 10 | handleTabChange = key => { 11 | const { dispatch, match } = this.props 12 | switch (key) { 13 | case 'articles': 14 | dispatch(routerRedux.push(`${match.url}/articles`)) 15 | break 16 | case 'applications': 17 | dispatch(routerRedux.push(`${match.url}/applications`)) 18 | break 19 | case 'projects': 20 | dispatch(routerRedux.push(`${match.url}/projects`)) 21 | break 22 | default: 23 | break 24 | } 25 | } 26 | 27 | render() { 28 | const tabList = [ 29 | { 30 | key: 'articles', 31 | tab: '文章', 32 | }, 33 | { 34 | key: 'applications', 35 | tab: '应用', 36 | }, 37 | { 38 | key: 'projects', 39 | tab: '项目', 40 | }, 41 | ] 42 | 43 | const mainSearch = ( 44 |
45 | 52 |
53 | ) 54 | 55 | const { match, routerData, location } = this.props 56 | const routes = getRoutes(match.path, routerData) 57 | 58 | return ( 59 | 66 | 67 | {routes.map(item => ( 68 | 69 | ))} 70 | 71 | 72 | 73 | ) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/pages/List/CardList.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .cardList { 5 | margin-bottom: -24px; 6 | 7 | .card { 8 | :global { 9 | .ant-card-meta-title { 10 | margin-bottom: 12px; 11 | & > a { 12 | color: @heading-color; 13 | display: inline-block; 14 | max-width: 100%; 15 | } 16 | } 17 | .ant-card-actions { 18 | background: #f7f9fa; 19 | } 20 | .ant-card-body:hover { 21 | .ant-card-meta-title > a { 22 | color: @primary-color; 23 | } 24 | } 25 | } 26 | } 27 | .item { 28 | height: 64px; 29 | } 30 | 31 | :global { 32 | .ant-list .ant-list-item-content-single { 33 | max-width: 100%; 34 | } 35 | } 36 | } 37 | 38 | .extraImg { 39 | margin-top: -60px; 40 | text-align: center; 41 | width: 195px; 42 | img { 43 | width: 100%; 44 | } 45 | } 46 | 47 | .newButton { 48 | background-color: #fff; 49 | border-color: @border-color-base; 50 | border-radius: @border-radius-sm; 51 | color: @text-color-secondary; 52 | width: 100%; 53 | height: 188px; 54 | } 55 | 56 | .cardAvatar { 57 | width: 48px; 58 | height: 48px; 59 | border-radius: 48px; 60 | } 61 | 62 | .cardDescription { 63 | .textOverflowMulti(); 64 | } 65 | 66 | .pageHeaderContent { 67 | position: relative; 68 | } 69 | 70 | .contentLink { 71 | margin-top: 16px; 72 | a { 73 | margin-right: 32px; 74 | img { 75 | width: 24px; 76 | } 77 | } 78 | img { 79 | vertical-align: middle; 80 | margin-right: 8px; 81 | } 82 | } 83 | 84 | @media screen and (max-width: @screen-lg) { 85 | .contentLink { 86 | a { 87 | margin-right: 16px; 88 | } 89 | } 90 | } 91 | @media screen and (max-width: @screen-md) { 92 | .extraImg { 93 | display: none; 94 | } 95 | } 96 | 97 | @media screen and (max-width: @screen-sm) { 98 | .pageHeaderContent { 99 | padding-bottom: 30px; 100 | } 101 | .contentLink { 102 | position: absolute; 103 | left: 0; 104 | bottom: -4px; 105 | width: 1000px; 106 | a { 107 | margin-right: 16px; 108 | } 109 | img { 110 | margin-right: 4px; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/components/ActiveChart/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import { MiniArea } from 'ant-design-pro/lib/Charts' 4 | import NumberInfo from 'ant-design-pro/lib/NumberInfo' 5 | 6 | import styles from './index.less' 7 | 8 | function fixedZero(val) { 9 | return val * 1 < 10 ? `0${val}` : val 10 | } 11 | 12 | function getActiveData() { 13 | const activeData = [] 14 | for (let i = 0; i < 24; i += 1) { 15 | activeData.push({ 16 | x: `${fixedZero(i)}:00`, 17 | y: Math.floor(Math.random() * 200) + i * 50, 18 | }) 19 | } 20 | return activeData 21 | } 22 | 23 | export default class ActiveChart extends Component { 24 | state = { 25 | activeData: getActiveData(), 26 | } 27 | 28 | componentDidMount() { 29 | this.timer = setInterval(() => { 30 | this.setState({ 31 | activeData: getActiveData(), 32 | }) 33 | }, 1000) 34 | } 35 | 36 | componentWillUnmount() { 37 | clearInterval(this.timer) 38 | } 39 | 40 | render() { 41 | const { activeData = [] } = this.state 42 | 43 | return ( 44 |
45 | 46 |
47 | 65 |
66 | {activeData && ( 67 |
68 |

{[...activeData].sort()[activeData.length - 1].y + 200} 亿元

69 |

{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元

70 |
71 | )} 72 | {activeData && ( 73 |
74 | 00:00 75 | {activeData[Math.floor(activeData.length / 2)].x} 76 | {activeData[activeData.length - 1].x} 77 |
78 | )} 79 |
80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import { Link, Redirect, Switch, Route } from 'dva/router' 3 | import DocumentTitle from 'react-document-title' 4 | import { Icon } from 'antd' 5 | import GlobalFooter from 'ant-design-pro/lib/GlobalFooter' 6 | import styles from './UserLayout.less' 7 | import logo from '../assets/logo.svg' 8 | import { getRoutes } from '../utils/utils' 9 | 10 | const links = [ 11 | { 12 | key: 'help', 13 | title: '帮助', 14 | href: '', 15 | }, 16 | { 17 | key: 'privacy', 18 | title: '隐私', 19 | href: '', 20 | }, 21 | { 22 | key: 'terms', 23 | title: '条款', 24 | href: '', 25 | }, 26 | ] 27 | 28 | const copyright = ( 29 | 30 | Copyright 2018 蚂蚁金服体验技术部出品 31 | 32 | ) 33 | 34 | class UserLayout extends React.PureComponent { 35 | getPageTitle() { 36 | const { routerData, location } = this.props 37 | const { pathname } = location 38 | let title = 'Ant Design Pro' 39 | if (routerData[pathname] && routerData[pathname].name) { 40 | title = `${routerData[pathname].name} - Ant Design Pro` 41 | } 42 | return title 43 | } 44 | render() { 45 | const { routerData, match } = this.props 46 | return ( 47 | 48 |
49 |
50 |
51 |
52 | 53 | logo 54 | Ant Design 55 | 56 |
57 |
Ant Design 是西湖区最具影响力的 Web 设计规范
58 |
59 | 60 | {getRoutes(match.path, routerData).map(item => ( 61 | 67 | ))} 68 | 69 | 70 |
71 | 72 |
73 |
74 | ) 75 | } 76 | } 77 | 78 | export default UserLayout 79 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 简体中文 2 | 3 | # Ant Design Pro 4 | 5 | [![](https://img.shields.io/travis/ant-design/ant-design-pro.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design-pro) [![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master) [![Gitter](https://badges.gitter.im/ant-design/ant-design-pro.svg)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 6 | 7 | 开箱即用的中台前端/设计解决方案。 8 | 9 | ![](/exImages/xEdBqwSzvoSapmnSnYjU.png) 10 | 11 | - 预览:http://preview.pro.ant.design 12 | - 首页:http://pro.ant.design/index-cn 13 | - 使用文档:http://pro.ant.design/docs/getting-started-cn 14 | - 更新日志: http://pro.ant.design/docs/changelog-cn 15 | - 常见问题:http://pro.ant.design/docs/faq-cn 16 | 17 | ## 特性 18 | 19 | - :gem: **优雅美观**:基于 Ant Design 体系精心设计 20 | - :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景 21 | - :rocket: **最新技术栈**:使用 React/dva/antd 等前端前沿技术开发 22 | - :iphone: **响应式**:针对不同屏幕大小设计 23 | - :art: **主题**:可配置的主题满足多样化的品牌诉求 24 | - :globe_with_meridians: **国际化**:内建业界通用的国际化方案 25 | - :gear: **最佳实践**:良好的工程实践助您持续产出高质量代码 26 | - :1234: **Mock 数据**:实用的本地数据调试方案 27 | - :white_check_mark: **UI 测试**:自动化测试保障前端产品质量 28 | 29 | ## 模板 30 | 31 | ``` 32 | - Dashboard 33 | - 分析页 34 | - 监控页 35 | - 工作台 36 | - 表单页 37 | - 基础表单页 38 | - 分步表单页 39 | - 高级表单页 40 | - 列表页 41 | - 查询表格 42 | - 标准列表 43 | - 卡片列表 44 | - 搜索列表(项目/应用/文章) 45 | - 详情页 46 | - 基础详情页 47 | - 高级详情页 48 | - 结果 49 | - 成功页 50 | - 失败页 51 | - 异常 52 | - 403 无权限 53 | - 404 找不到 54 | - 500 服务器出错 55 | - 帐户 56 | - 登录 57 | - 注册 58 | - 注册成功 59 | ``` 60 | 61 | ## 使用 62 | 63 | ```bash 64 | $ git clone https://github.com/ant-design/ant-design-pro.git --depth=1 65 | $ cd ant-design-pro 66 | $ npm install 67 | $ npm start # 访问 http://localhost:8000 68 | ``` 69 | 70 | 也可以使用集成化的 [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli) 工具。 71 | 72 | ```bash 73 | $ npm install ant-design-pro-cli -g 74 | $ mkdir pro-demo && cd pro-demo 75 | $ pro new 76 | ``` 77 | 78 | 更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)。 79 | 80 | ## 兼容性 81 | 82 | 现代浏览器及 IE11。 83 | 84 | ## 参与贡献 85 | 86 | 我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley:: 87 | 88 | - 在你的公司或个人项目中使用 Ant Design Pro。 89 | - 通过 [Issue](http://github.com/ant-design/ant-design-pro/issues) 报告 bug 或进行咨询。 90 | - 提交 [Pull Request](http://github.com/ant-design/ant-design-pro/pulls) 改进 Pro 的代码。 91 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["airbnb", "prettier"], 4 | "plugins": ["prettier"], 5 | "env": { 6 | "browser": true, 7 | "node": true, 8 | "es6": true, 9 | "mocha": true, 10 | "jest": true, 11 | "jasmine": true 12 | }, 13 | "rules": { 14 | "generator-star-spacing": [0], 15 | "consistent-return": [0], 16 | "react/forbid-prop-types": [0], 17 | "react/jsx-filename-extension": [1, { "extensions": [".js"] }], 18 | "global-require": [1], 19 | "import/prefer-default-export": [0], 20 | "react/jsx-no-bind": [0], 21 | "react/prop-types": [0], 22 | "react/prefer-stateless-function": [0], 23 | "react/jsx-wrap-multilines": [ 24 | "error", 25 | { 26 | "declaration": "parens-new-line", 27 | "assignment": "parens-new-line", 28 | "return": "parens-new-line", 29 | "arrow": "parens-new-line", 30 | "condition": "parens-new-line", 31 | "logical": "parens-new-line", 32 | "prop": "ignore" 33 | } 34 | ], 35 | "no-else-return": [0], 36 | "no-restricted-syntax": [0], 37 | "import/no-extraneous-dependencies": [0], 38 | "no-use-before-define": [0], 39 | "jsx-a11y/no-static-element-interactions": [0], 40 | "jsx-a11y/no-noninteractive-element-interactions": [0], 41 | "jsx-a11y/click-events-have-key-events": [0], 42 | "jsx-a11y/anchor-is-valid": [0], 43 | "no-nested-ternary": [0], 44 | "arrow-body-style": [0], 45 | "import/extensions": [0], 46 | "no-bitwise": [0], 47 | "no-cond-assign": [0], 48 | "import/no-unresolved": [0], 49 | "comma-dangle": [ 50 | "error", 51 | { 52 | "arrays": "always-multiline", 53 | "objects": "always-multiline", 54 | "imports": "always-multiline", 55 | "exports": "always-multiline", 56 | "functions": "ignore" 57 | } 58 | ], 59 | "object-curly-newline": [0], 60 | "function-paren-newline": [0], 61 | "no-restricted-globals": [0], 62 | "require-yield": [1], 63 | "object-shorthand": [0], 64 | "func-names": [0], 65 | "semi": [2, "never"], 66 | "import/first": [0], 67 | "no-multi-spaces": [0], 68 | "no-underscore-dangle": ["error", { "allow": ["_store"] }], 69 | "prettier/prettier": "error" 70 | }, 71 | "parserOptions": { 72 | "ecmaFeatures": { 73 | "experimentalObjectRestSpread": true 74 | } 75 | }, 76 | "settings": { 77 | "polyfills": ["fetch", "promises"] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/GlobalHeader/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .header { 4 | height: 64px; 5 | padding: 0 12px 0 0; 6 | background: #fff; 7 | box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); 8 | position: relative; 9 | } 10 | 11 | :global { 12 | .ant-layout { 13 | min-height: 100vh; 14 | overflow-x: hidden; 15 | } 16 | } 17 | 18 | .logo { 19 | height: 64px; 20 | line-height: 58px; 21 | vertical-align: top; 22 | display: inline-block; 23 | padding: 0 0 0 24px; 24 | cursor: pointer; 25 | font-size: 20px; 26 | img { 27 | display: inline-block; 28 | vertical-align: middle; 29 | } 30 | } 31 | 32 | .menu { 33 | :global(.anticon) { 34 | margin-right: 8px; 35 | } 36 | :global(.ant-dropdown-menu-item) { 37 | width: 160px; 38 | } 39 | } 40 | 41 | i.trigger { 42 | font-size: 20px; 43 | line-height: 64px; 44 | cursor: pointer; 45 | transition: all 0.3s, padding 0s; 46 | padding: 0 24px; 47 | &:hover { 48 | background: @primary-1; 49 | } 50 | } 51 | 52 | .right { 53 | float: right; 54 | height: 100%; 55 | .action { 56 | cursor: pointer; 57 | padding: 0 12px; 58 | display: inline-block; 59 | transition: all 0.3s; 60 | height: 100%; 61 | > i { 62 | font-size: 16px; 63 | vertical-align: middle; 64 | color: @text-color; 65 | } 66 | &:hover, 67 | &:global(.ant-popover-open) { 68 | background: @primary-1; 69 | } 70 | } 71 | .search { 72 | padding: 0; 73 | margin: 0 12px; 74 | &:hover { 75 | background: transparent; 76 | } 77 | } 78 | .account { 79 | .avatar { 80 | margin: 20px 8px 20px 0; 81 | color: @primary-color; 82 | background: rgba(255, 255, 255, 0.85); 83 | vertical-align: middle; 84 | } 85 | } 86 | } 87 | 88 | @media only screen and (max-width: @screen-md) { 89 | .header { 90 | :global(.ant-divider-vertical) { 91 | vertical-align: unset; 92 | } 93 | .name { 94 | display: none; 95 | } 96 | i.trigger { 97 | padding: 0 12px; 98 | } 99 | .logo { 100 | padding-right: 12px; 101 | position: relative; 102 | } 103 | .right { 104 | position: absolute; 105 | right: 12px; 106 | top: 0; 107 | background: #fff; 108 | .account { 109 | .avatar { 110 | margin-right: 0; 111 | } 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /mock/notices.js: -------------------------------------------------------------------------------- 1 | export const getNotices = (req, res) => { 2 | res.json([ 3 | { 4 | id: '000000001', 5 | avatar: '/exImages/ThXAXghbEsBCCSDihZxY.png', 6 | title: '你收到了 14 份新周报', 7 | datetime: '2017-08-09', 8 | type: '通知', 9 | }, 10 | { 11 | id: '000000002', 12 | avatar: '/exImages/OKJXDXrmkNshAMvwtvhu.png', 13 | title: '你推荐的 曲妮妮 已通过第三轮面试', 14 | datetime: '2017-08-08', 15 | type: '通知', 16 | }, 17 | { 18 | id: '000000003', 19 | avatar: '/exImages/kISTdvpyTAhtGxpovNWd.png', 20 | title: '这种模板可以区分多种通知类型', 21 | datetime: '2017-08-07', 22 | read: true, 23 | type: '通知', 24 | }, 25 | { 26 | id: '000000004', 27 | avatar: '/exImages/GvqBnKhFgObvnSGkDsje.png', 28 | title: '左侧图标用于区分不同的类型', 29 | datetime: '2017-08-07', 30 | type: '通知', 31 | }, 32 | { 33 | id: '000000005', 34 | avatar: '/exImages/ThXAXghbEsBCCSDihZxY.png', 35 | title: '内容不要超过两行字,超出时自动截断', 36 | datetime: '2017-08-07', 37 | type: '通知', 38 | }, 39 | { 40 | id: '000000006', 41 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', 42 | title: '曲丽丽 评论了你', 43 | description: '描述信息描述信息描述信息', 44 | datetime: '2017-08-07', 45 | type: '消息', 46 | }, 47 | { 48 | id: '000000007', 49 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', 50 | title: '朱偏右 回复了你', 51 | description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', 52 | datetime: '2017-08-07', 53 | type: '消息', 54 | }, 55 | { 56 | id: '000000008', 57 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', 58 | title: '标题', 59 | description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', 60 | datetime: '2017-08-07', 61 | type: '消息', 62 | }, 63 | { 64 | id: '000000009', 65 | title: '任务名称', 66 | description: '任务需要在 2017-01-12 20:00 前启动', 67 | extra: '未开始', 68 | status: 'todo', 69 | type: '待办', 70 | }, 71 | { 72 | id: '000000010', 73 | title: '第三方紧急代码变更', 74 | description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', 75 | extra: '马上到期', 76 | status: 'urgent', 77 | type: '待办', 78 | }, 79 | { 80 | id: '000000011', 81 | title: '信息安全考试', 82 | description: '指派竹尔于 2017-01-09 前完成更新并发布', 83 | extra: '已耗时 8 天', 84 | status: 'doing', 85 | type: '待办', 86 | }, 87 | { 88 | id: '000000012', 89 | title: 'ABCD 版本发布', 90 | description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', 91 | extra: '进行中', 92 | status: 'processing', 93 | type: '待办', 94 | }, 95 | ]) 96 | } 97 | export default { 98 | 'GET /api/notices': getNotices, 99 | } 100 | -------------------------------------------------------------------------------- /src/pages/Result/Success.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import { Button, Row, Col, Icon, Steps, Card } from 'antd' 3 | import Result from 'ant-design-pro/lib/Result' 4 | import PageHeaderLayout from '../../layouts/PageHeaderLayout' 5 | 6 | const { Step } = Steps 7 | 8 | const desc1 = ( 9 |
17 |
18 | 曲丽丽 19 |
20 |
2016-12-12 12:32
21 |
22 | ) 23 | 24 | const desc2 = ( 25 |
26 |
27 | 周毛毛 28 |
29 |
30 | 催一下 31 |
32 |
33 | ) 34 | 35 | const extra = ( 36 | 37 |
45 | 项目名称 46 |
47 | 48 | 49 | 项目 ID: 50 | 23421 51 | 52 | 53 | 负责人: 54 | 曲丽丽 55 | 56 | 57 | 生效时间: 58 | 2016-12-12 ~ 2017-12-12 59 | 60 | 61 | 62 | 创建项目} description={desc1} /> 63 | 部门初审} description={desc2} /> 64 | 财务复核} /> 65 | 完成} /> 66 | 67 |
68 | ) 69 | 70 | const actions = ( 71 | 72 | 73 | 74 | 75 | 76 | ) 77 | 78 | export default () => ( 79 | 80 | 81 | 92 | 93 | 94 | ) 95 | -------------------------------------------------------------------------------- /src/pages/List/CardList.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import { connect } from 'dva' 3 | import { Card, Button, Icon, List } from 'antd' 4 | 5 | import Ellipsis from 'ant-design-pro/lib/Ellipsis' 6 | import PageHeaderLayout from '../../layouts/PageHeaderLayout' 7 | 8 | import styles from './CardList.less' 9 | 10 | @connect(({ list, loading }) => ({ 11 | list, 12 | loading: loading.models.list, 13 | })) 14 | export default class CardList extends PureComponent { 15 | componentDidMount() { 16 | this.props.dispatch({ 17 | type: 'list/fetch', 18 | payload: { 19 | count: 8, 20 | }, 21 | }) 22 | } 23 | 24 | render() { 25 | const { list: { list }, loading } = this.props 26 | 27 | const content = ( 28 |
29 |

30 | 段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态, 31 | 提供跨越设计与开发的体验解决方案。 32 |

33 | 44 |
45 | ) 46 | 47 | const extraContent = ( 48 |
49 | 这是一个标题 50 |
51 | ) 52 | 53 | return ( 54 | 55 |
56 | 62 | item ? ( 63 | 64 | 操作一, 操作二]}> 65 | } 67 | title={{item.title}} 68 | description={ 69 | 70 | {item.description} 71 | 72 | } 73 | /> 74 | 75 | 76 | ) : ( 77 | 78 | 81 | 82 | ) 83 | } 84 | /> 85 |
86 |
87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/pages/User/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'dva' 3 | import { Link } from 'dva/router' 4 | import { Checkbox, Alert, Icon } from 'antd' 5 | import Login from 'src/components/Login' 6 | import styles from './Login.less' 7 | 8 | const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login 9 | 10 | @connect(({ login, loading }) => ({ 11 | login, 12 | submitting: loading.effects['login/login'], 13 | })) 14 | export default class LoginPage extends Component { 15 | state = { 16 | type: 'account', 17 | autoLogin: true, 18 | } 19 | 20 | onTabChange = type => { 21 | this.setState({ type }) 22 | } 23 | 24 | handleSubmit = (err, values) => { 25 | const { type } = this.state 26 | if (!err) { 27 | this.props.dispatch({ 28 | type: 'login/login', 29 | payload: { 30 | ...values, 31 | type, 32 | }, 33 | }) 34 | } 35 | } 36 | 37 | changeAutoLogin = e => { 38 | this.setState({ 39 | autoLogin: e.target.checked, 40 | }) 41 | } 42 | 43 | renderMessage = content => { 44 | return 45 | } 46 | 47 | render() { 48 | const { login, submitting } = this.props 49 | const { type } = this.state 50 | return ( 51 |
52 | 53 | 54 | {login.status === 'error' && 55 | login.type === 'account' && 56 | !login.submitting && 57 | this.renderMessage('账户或密码错误(admin/888888)')} 58 | 59 | 60 | 61 | 62 | {login.status === 'error' && 63 | login.type === 'mobile' && 64 | !login.submitting && 65 | this.renderMessage('验证码错误')} 66 | 67 | 68 | 69 |
70 | 71 | 自动登录 72 | 73 | 74 | 忘记密码 75 | 76 |
77 | 登录 78 |
79 | 其他登录方式 80 | 81 | 82 | 83 | 84 | 注册账户 85 | 86 |
87 |
88 |
89 | ) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /public/exImages/ohOEPSYdDTNnyMbGuyLb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 3 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/pages/Forms/StepForm/Step2.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'dva' 3 | import { Form, Input, Button, Alert, Divider } from 'antd' 4 | import { routerRedux } from 'dva/router' 5 | import { digitUppercase } from '../../../utils/utils' 6 | import styles from './style.less' 7 | 8 | const formItemLayout = { 9 | labelCol: { 10 | span: 5, 11 | }, 12 | wrapperCol: { 13 | span: 19, 14 | }, 15 | } 16 | 17 | @Form.create() 18 | class Step2 extends React.PureComponent { 19 | render() { 20 | const { form, data, dispatch, submitting } = this.props 21 | const { getFieldDecorator, validateFields } = form 22 | const onPrev = () => { 23 | dispatch(routerRedux.push('/form/step-form')) 24 | } 25 | const onValidateForm = e => { 26 | e.preventDefault() 27 | validateFields((err, values) => { 28 | if (!err) { 29 | dispatch({ 30 | type: 'form/submitStepForm', 31 | payload: { 32 | ...data, 33 | ...values, 34 | }, 35 | }) 36 | } 37 | }) 38 | } 39 | return ( 40 |
41 | 47 | 48 | {data.payAccount} 49 | 50 | 51 | {data.receiverAccount} 52 | 53 | 54 | {data.receiverName} 55 | 56 | 57 | {data.amount} 58 | ({digitUppercase(data.amount)}) 59 | 60 | 61 | 62 | {getFieldDecorator('password', { 63 | initialValue: '123456', 64 | rules: [ 65 | { 66 | required: true, 67 | message: '需要支付密码才能进行支付', 68 | }, 69 | ], 70 | })()} 71 | 72 | 83 | 86 | 89 | 90 | 91 | ) 92 | } 93 | } 94 | 95 | export default connect(({ form, loading }) => ({ 96 | submitting: loading.effects['form/submitStepForm'], 97 | data: form.step, 98 | }))(Step2) 99 | -------------------------------------------------------------------------------- /src/components/Login/LoginItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Form, Button, Row, Col } from 'antd' 4 | import omit from 'omit.js' 5 | import styles from './index.less' 6 | import map from './map' 7 | 8 | const FormItem = Form.Item 9 | 10 | function generator({ defaultProps, defaultRules, type }) { 11 | return WrappedComponent => { 12 | return class BasicComponent extends Component { 13 | static contextTypes = { 14 | form: PropTypes.object, 15 | updateActive: PropTypes.func, 16 | } 17 | constructor(props) { 18 | super(props) 19 | this.state = { 20 | count: 0, 21 | } 22 | } 23 | componentDidMount() { 24 | if (this.context.updateActive) { 25 | this.context.updateActive(this.props.name) 26 | } 27 | } 28 | componentWillUnmount() { 29 | clearInterval(this.interval) 30 | } 31 | onGetCaptcha = () => { 32 | let count = 59 33 | this.setState({ count }) 34 | if (this.props.onGetCaptcha) { 35 | this.props.onGetCaptcha() 36 | } 37 | this.interval = setInterval(() => { 38 | count -= 1 39 | this.setState({ count }) 40 | if (count === 0) { 41 | clearInterval(this.interval) 42 | } 43 | }, 1000) 44 | } 45 | render() { 46 | const { getFieldDecorator } = this.context.form 47 | const options = {} 48 | let otherProps = {} 49 | const { onChange, defaultValue, rules, name, ...restProps } = this.props 50 | const { count } = this.state 51 | options.rules = rules || defaultRules 52 | if (onChange) { 53 | options.onChange = onChange 54 | } 55 | if (defaultValue) { 56 | options.initialValue = defaultValue 57 | } 58 | otherProps = restProps || otherProps 59 | if (type === 'Captcha') { 60 | const inputProps = omit(otherProps, ['onGetCaptcha']) 61 | return ( 62 | 63 | 64 | 65 | {getFieldDecorator(name, options)( 66 | 67 | )} 68 | 69 | 70 | 78 | 79 | 80 | 81 | ) 82 | } 83 | return ( 84 | 85 | {getFieldDecorator(name, options)( 86 | 87 | )} 88 | 89 | ) 90 | } 91 | } 92 | } 93 | } 94 | 95 | const LoginItem = {} 96 | Object.keys(map).forEach(item => { 97 | LoginItem[item] = generator({ 98 | defaultProps: map[item].props, 99 | defaultRules: map[item].rules, 100 | type: item, 101 | })(map[item].component) 102 | }) 103 | 104 | export default LoginItem 105 | -------------------------------------------------------------------------------- /mock/profile.js: -------------------------------------------------------------------------------- 1 | const basicGoods = [ 2 | { 3 | id: '1234561', 4 | name: '矿泉水 550ml', 5 | barcode: '12421432143214321', 6 | price: '2.00', 7 | num: '1', 8 | amount: '2.00', 9 | }, 10 | { 11 | id: '1234562', 12 | name: '凉茶 300ml', 13 | barcode: '12421432143214322', 14 | price: '3.00', 15 | num: '2', 16 | amount: '6.00', 17 | }, 18 | { 19 | id: '1234563', 20 | name: '好吃的薯片', 21 | barcode: '12421432143214323', 22 | price: '7.00', 23 | num: '4', 24 | amount: '28.00', 25 | }, 26 | { 27 | id: '1234564', 28 | name: '特别好吃的蛋卷', 29 | barcode: '12421432143214324', 30 | price: '8.50', 31 | num: '3', 32 | amount: '25.50', 33 | }, 34 | ] 35 | 36 | const basicProgress = [ 37 | { 38 | key: '1', 39 | time: '2017-10-01 14:10', 40 | rate: '联系客户', 41 | status: 'processing', 42 | operator: '取货员 ID1234', 43 | cost: '5mins', 44 | }, 45 | { 46 | key: '2', 47 | time: '2017-10-01 14:05', 48 | rate: '取货员出发', 49 | status: 'success', 50 | operator: '取货员 ID1234', 51 | cost: '1h', 52 | }, 53 | { 54 | key: '3', 55 | time: '2017-10-01 13:05', 56 | rate: '取货员接单', 57 | status: 'success', 58 | operator: '取货员 ID1234', 59 | cost: '5mins', 60 | }, 61 | { 62 | key: '4', 63 | time: '2017-10-01 13:00', 64 | rate: '申请审批通过', 65 | status: 'success', 66 | operator: '系统', 67 | cost: '1h', 68 | }, 69 | { 70 | key: '5', 71 | time: '2017-10-01 12:00', 72 | rate: '发起退货申请', 73 | status: 'success', 74 | operator: '用户', 75 | cost: '5mins', 76 | }, 77 | ] 78 | 79 | const advancedOperation1 = [ 80 | { 81 | key: 'op1', 82 | type: '订购关系生效', 83 | name: '曲丽丽', 84 | status: 'agree', 85 | updatedAt: '2017-10-03 19:23:12', 86 | memo: '-', 87 | }, 88 | { 89 | key: 'op2', 90 | type: '财务复审', 91 | name: '付小小', 92 | status: 'reject', 93 | updatedAt: '2017-10-03 19:23:12', 94 | memo: '不通过原因', 95 | }, 96 | { 97 | key: 'op3', 98 | type: '部门初审', 99 | name: '周毛毛', 100 | status: 'agree', 101 | updatedAt: '2017-10-03 19:23:12', 102 | memo: '-', 103 | }, 104 | { 105 | key: 'op4', 106 | type: '提交订单', 107 | name: '林东东', 108 | status: 'agree', 109 | updatedAt: '2017-10-03 19:23:12', 110 | memo: '很棒', 111 | }, 112 | { 113 | key: 'op5', 114 | type: '创建订单', 115 | name: '汗牙牙', 116 | status: 'agree', 117 | updatedAt: '2017-10-03 19:23:12', 118 | memo: '-', 119 | }, 120 | ] 121 | 122 | const advancedOperation2 = [ 123 | { 124 | key: 'op1', 125 | type: '订购关系生效', 126 | name: '曲丽丽', 127 | status: 'agree', 128 | updatedAt: '2017-10-03 19:23:12', 129 | memo: '-', 130 | }, 131 | ] 132 | 133 | const advancedOperation3 = [ 134 | { 135 | key: 'op1', 136 | type: '创建订单', 137 | name: '汗牙牙', 138 | status: 'agree', 139 | updatedAt: '2017-10-03 19:23:12', 140 | memo: '-', 141 | }, 142 | ] 143 | 144 | export const getProfileBasicData = { 145 | basicGoods, 146 | basicProgress, 147 | } 148 | 149 | export const getProfileAdvancedData = { 150 | advancedOperation1, 151 | advancedOperation2, 152 | advancedOperation3, 153 | } 154 | 155 | export default { 156 | 'GET /api/profile/basic': getProfileBasicData, 157 | 'GET /api/profile/advanced': getProfileAdvancedData, 158 | } 159 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ant-design-pro", 3 | "version": "1.2.1", 4 | "description": "An out-of-box UI solution for enterprise applications", 5 | "private": true, 6 | "scripts": { 7 | "clean": "rm -rf node_modules/ && yarn", 8 | "precommit": "lint-staged", 9 | "start": "cross-env ESLINT=none roadhog dev", 10 | "start:no-proxy": "cross-env NO_PROXY=true ESLINT=none roadhog dev", 11 | "build": "cross-env ESLINT=none roadhog build", 12 | "site": "roadhog-api-doc static && gh-pages -d dist", 13 | "analyze": "cross-env ANALYZE=true roadhog build", 14 | "lint:style": "stylelint \"src/**/*.less\" --syntax less", 15 | "lint": "eslint --ext .js src mock tests && npm run lint:style", 16 | "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style", 17 | "lint-staged:js": "eslint --ext .js", 18 | "test": "roadhog test", 19 | "test:component": "roadhog test ./src/components", 20 | "test:all": "node ./tests/run-tests.js", 21 | "format": "prettier --write './src/**/*.{less,js}'" 22 | }, 23 | "dependencies": { 24 | "@babel/polyfill": "^7.0.0-beta.44", 25 | "@types/enzyme": "^3.1.9", 26 | "@types/jest": "^22.2.2", 27 | "@types/node": "^9.6.2", 28 | "@types/react": "^16.3.5", 29 | "@types/react-dom": "^16.0.4", 30 | "ant-design-pro": "^1.2.1", 31 | "antd": "^3.4.0", 32 | "dva": "^2.1.0", 33 | "dva-loading": "^1.0.4", 34 | "react": "^16.3.1", 35 | "react-dom": "^16.3.1", 36 | "react-error-boundary": "^1.2.1", 37 | "redux-persist": "^5.9.1" 38 | }, 39 | "devDependencies": { 40 | "@types/eslint-plugin-prettier": "^2.2.0", 41 | "@types/prettier": "^1.10.0", 42 | "babel-eslint": "^8.2.2", 43 | "babel-plugin-dva-hmr": "^0.4.1", 44 | "babel-plugin-import": "^1.7.0", 45 | "babel-plugin-module-resolver": "^3.1.1", 46 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 47 | "cross-env": "^5.1.4", 48 | "cross-port-killer": "^1.0.1", 49 | "enzyme": "^3.3.0", 50 | "eslint": "^4.19.1", 51 | "eslint-config-airbnb": "^16.1.0", 52 | "eslint-config-prettier": "^2.9.0", 53 | "eslint-plugin-babel": "^5.0.0", 54 | "eslint-plugin-compat": "^2.2.0", 55 | "eslint-plugin-import": "^2.10.0", 56 | "eslint-plugin-jsx-a11y": "^6.0.3", 57 | "eslint-plugin-markdown": "^1.0.0-beta.6", 58 | "eslint-plugin-prettier": "^2.6.0", 59 | "eslint-plugin-react": "^7.7.0", 60 | "gh-pages": "^1.1.0", 61 | "husky": "^0.14.3", 62 | "lint-staged": "^7.0.3", 63 | "mockjs": "^1.0.1-beta3", 64 | "optimize-css-assets-webpack-plugin": "^3.2.0", 65 | "prettier": "1.11.1", 66 | "pro-download": "^1.0.1", 67 | "redbox-react": "^1.5.0", 68 | "regenerator-runtime": "^0.11.1", 69 | "roadhog": "^2.3.0", 70 | "roadhog-api-doc": "^1.0.0", 71 | "rollbar": "^2.3.9", 72 | "stylelint": "^9.2.0", 73 | "stylelint-config-prettier": "^3.0.4", 74 | "stylelint-config-standard": "^18.2.0" 75 | }, 76 | "optionalDependencies": { 77 | "puppeteer": "^1.1.1" 78 | }, 79 | "lint-staged": { 80 | "src/*.{js,jsx}": [ 81 | "prettier --wirte", 82 | "eslint --fix", 83 | "git add" 84 | ], 85 | "src/**/*.less": [ 86 | "prettier --wirte", 87 | "git add", 88 | "stylelint --syntax less" 89 | ] 90 | }, 91 | "engines": { 92 | "node": ">=8.0.0" 93 | }, 94 | "browserslist": [ 95 | "> 1%", 96 | "last 2 versions", 97 | "not ie <= 10" 98 | ] 99 | } 100 | -------------------------------------------------------------------------------- /src/components/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Form, Tabs } from 'antd' 4 | import classNames from 'classnames' 5 | import LoginItem from './LoginItem' 6 | import LoginTab from './LoginTab' 7 | import LoginSubmit from './LoginSubmit' 8 | import styles from './index.less' 9 | 10 | @Form.create() 11 | class Login extends Component { 12 | static defaultProps = { 13 | className: '', 14 | defaultActiveKey: '', 15 | onTabChange: () => {}, 16 | onSubmit: () => {}, 17 | } 18 | static propTypes = { 19 | className: PropTypes.string, 20 | defaultActiveKey: PropTypes.string, 21 | onTabChange: PropTypes.func, 22 | onSubmit: PropTypes.func, 23 | } 24 | static childContextTypes = { 25 | tabUtil: PropTypes.object, 26 | form: PropTypes.object, 27 | updateActive: PropTypes.func, 28 | } 29 | state = { 30 | type: this.props.defaultActiveKey, 31 | tabs: [], 32 | active: {}, 33 | } 34 | getChildContext() { 35 | return { 36 | tabUtil: { 37 | addTab: id => { 38 | this.setState({ 39 | tabs: [...this.state.tabs, id], 40 | }) 41 | }, 42 | removeTab: id => { 43 | this.setState({ 44 | tabs: this.state.tabs.filter(currentId => currentId !== id), 45 | }) 46 | }, 47 | }, 48 | form: this.props.form, 49 | updateActive: activeItem => { 50 | const { type, active } = this.state 51 | if (active[type]) { 52 | active[type].push(activeItem) 53 | } else { 54 | active[type] = [activeItem] 55 | } 56 | this.setState({ 57 | active, 58 | }) 59 | }, 60 | } 61 | } 62 | onSwitch = type => { 63 | this.setState({ 64 | type, 65 | }) 66 | this.props.onTabChange(type) 67 | } 68 | handleSubmit = e => { 69 | e.preventDefault() 70 | const { active, type } = this.state 71 | const activeFileds = active[type] 72 | this.props.form.validateFields(activeFileds, { force: true }, (err, values) => { 73 | this.props.onSubmit(err, values) 74 | }) 75 | } 76 | render() { 77 | const { className, children } = this.props 78 | const { type, tabs } = this.state 79 | const TabChildren = [] 80 | const otherChildren = [] 81 | React.Children.forEach(children, item => { 82 | if (!item) { 83 | return 84 | } 85 | // eslint-disable-next-line 86 | if (item.type.__ANT_PRO_LOGIN_TAB) { 87 | TabChildren.push(item) 88 | } else { 89 | otherChildren.push(item) 90 | } 91 | }) 92 | return ( 93 |
94 |
95 | {tabs.length ? ( 96 |
97 | 103 | {TabChildren} 104 | 105 | {otherChildren} 106 |
107 | ) : ( 108 | [...children] 109 | )} 110 |
111 |
112 | ) 113 | } 114 | } 115 | 116 | Login.Tab = LoginTab 117 | Login.Submit = LoginSubmit 118 | Object.keys(LoginItem).forEach(item => { 119 | Login[item] = LoginItem[item] 120 | }) 121 | 122 | export default Login 123 | -------------------------------------------------------------------------------- /src/components/StandardTable/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent, Fragment } from 'react' 2 | import { Table, Alert } from 'antd' 3 | import styles from './index.less' 4 | 5 | function initTotalList(columns) { 6 | const totalList = [] 7 | columns.forEach(column => { 8 | if (column.needTotal) { 9 | totalList.push({ ...column, total: 0 }) 10 | } 11 | }) 12 | return totalList 13 | } 14 | 15 | class StandardTable extends PureComponent { 16 | static getDerivedStateFromProps(nextProps) { 17 | if (nextProps.selectedRows.length === 0) { 18 | const needTotalList = initTotalList(nextProps.columns) 19 | return { 20 | selectedRowKeys: [], 21 | needTotalList, 22 | } 23 | } 24 | return null 25 | } 26 | state = { 27 | selectedRowKeys: [], 28 | needTotalList: initTotalList(this.props.columns), 29 | } 30 | 31 | handleRowSelectChange = (selectedRowKeys, selectedRows) => { 32 | let needTotalList = [...this.state.needTotalList] 33 | needTotalList = needTotalList.map(item => { 34 | return { 35 | ...item, 36 | total: selectedRows.reduce((sum, val) => { 37 | return sum + parseFloat(val[item.dataIndex], 10) 38 | }, 0), 39 | } 40 | }) 41 | 42 | if (this.props.onSelectRow) { 43 | this.props.onSelectRow(selectedRows) 44 | } 45 | 46 | this.setState({ selectedRowKeys, needTotalList }) 47 | } 48 | 49 | handleTableChange = (pagination, filters, sorter) => { 50 | this.props.onChange(pagination, filters, sorter) 51 | } 52 | 53 | cleanSelectedKeys = () => { 54 | this.handleRowSelectChange([], []) 55 | } 56 | 57 | render() { 58 | const { selectedRowKeys, needTotalList } = this.state 59 | const { data: { list, pagination }, loading, columns } = this.props 60 | 61 | const paginationProps = { 62 | showSizeChanger: true, 63 | showQuickJumper: true, 64 | ...pagination, 65 | } 66 | 67 | const rowSelection = { 68 | selectedRowKeys, 69 | onChange: this.handleRowSelectChange, 70 | getCheckboxProps: record => ({ 71 | disabled: record.disabled, 72 | }), 73 | } 74 | 75 | return ( 76 |
77 |
78 | 81 | 已选择 {selectedRowKeys.length} 项   82 | {needTotalList.map(item => ( 83 | 84 | {item.title}总计  85 | 86 | {item.render ? item.render(item.total) : item.total} 87 | 88 | 89 | ))} 90 | 91 | 清空 92 | 93 | 94 | } 95 | type="info" 96 | showIcon 97 | /> 98 |
99 | record.key} 102 | rowSelection={rowSelection} 103 | dataSource={list} 104 | columns={columns} 105 | pagination={paginationProps} 106 | onChange={this.handleTableChange} 107 | /> 108 | 109 | ) 110 | } 111 | } 112 | 113 | export default StandardTable 114 | -------------------------------------------------------------------------------- /src/pages/Dashboard/Analysis.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .iconGroup { 5 | i { 6 | transition: color 0.32s; 7 | color: @text-color-secondary; 8 | cursor: pointer; 9 | margin-left: 16px; 10 | &:hover { 11 | color: @text-color; 12 | } 13 | } 14 | } 15 | 16 | .rankingList { 17 | margin: 25px 0 0; 18 | padding: 0; 19 | list-style: none; 20 | li { 21 | .clearfix(); 22 | margin-top: 16px; 23 | span { 24 | color: @text-color; 25 | font-size: 14px; 26 | line-height: 22px; 27 | } 28 | span:first-child { 29 | background-color: @background-color-base; 30 | border-radius: 20px; 31 | display: inline-block; 32 | font-size: 12px; 33 | font-weight: 600; 34 | margin-right: 24px; 35 | height: 20px; 36 | line-height: 20px; 37 | width: 20px; 38 | text-align: center; 39 | } 40 | span.active { 41 | //background-color: @primary-color; 42 | background-color: #314659; 43 | color: #fff; 44 | } 45 | span:last-child { 46 | float: right; 47 | } 48 | } 49 | } 50 | 51 | .salesExtra { 52 | display: inline-block; 53 | margin-right: 24px; 54 | a { 55 | color: @text-color; 56 | margin-left: 24px; 57 | &:hover { 58 | color: @primary-color; 59 | } 60 | &.currentDate { 61 | color: @primary-color; 62 | } 63 | } 64 | } 65 | 66 | .salesCard { 67 | .salesBar { 68 | padding: 0 0 32px 32px; 69 | } 70 | .salesRank { 71 | padding: 0 32px 32px 72px; 72 | } 73 | :global { 74 | .ant-tabs-bar { 75 | padding-left: 16px; 76 | .ant-tabs-nav .ant-tabs-tab { 77 | padding-top: 16px; 78 | padding-bottom: 14px; 79 | line-height: 24px; 80 | } 81 | } 82 | .ant-tabs-extra-content { 83 | padding-right: 24px; 84 | line-height: 55px; 85 | } 86 | .ant-card-head { 87 | position: relative; 88 | } 89 | } 90 | } 91 | 92 | .salesCardExtra { 93 | height: 68px; 94 | } 95 | 96 | .salesTypeRadio { 97 | position: absolute; 98 | left: 24px; 99 | bottom: 15px; 100 | } 101 | 102 | .offlineCard { 103 | :global { 104 | .ant-tabs-ink-bar { 105 | bottom: auto; 106 | } 107 | .ant-tabs-bar { 108 | border-bottom: none; 109 | } 110 | .ant-tabs-nav-container-scrolling { 111 | padding-left: 40px; 112 | padding-right: 40px; 113 | } 114 | .ant-tabs-tab-prev-icon:before { 115 | position: relative; 116 | left: 6px; 117 | } 118 | .ant-tabs-tab-next-icon:before { 119 | position: relative; 120 | right: 6px; 121 | } 122 | } 123 | 124 | :global(.ant-tabs-tab-active) h4 { 125 | color: @primary-color; 126 | } 127 | } 128 | 129 | .trendText { 130 | margin-left: 8px; 131 | color: @heading-color; 132 | } 133 | 134 | @media screen and (max-width: @screen-lg) { 135 | .salesExtra { 136 | display: none; 137 | } 138 | 139 | .rankingList { 140 | li { 141 | span:first-child { 142 | margin-right: 8px; 143 | } 144 | } 145 | } 146 | } 147 | 148 | @media screen and (max-width: @screen-md) { 149 | .rankingTitle { 150 | margin-top: 16px; 151 | } 152 | 153 | .salesCard .salesBar { 154 | padding: 16px; 155 | } 156 | } 157 | 158 | @media screen and (max-width: @screen-sm) { 159 | .salesExtraWrap { 160 | display: none; 161 | } 162 | 163 | .salesCard { 164 | :global { 165 | .ant-tabs-content { 166 | padding-top: 30px; 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/components/Login/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 标准登录 5 | en-US: Standard Login 6 | --- 7 | 8 | Support login with account and mobile number. 9 | 10 | ````jsx 11 | import Login from 'src/components/Login'; 12 | import { Alert, Checkbox } from 'antd'; 13 | 14 | const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login; 15 | 16 | class LoginDemo extends React.Component { 17 | state = { 18 | notice: '', 19 | type: 'tab2', 20 | autoLogin: true, 21 | } 22 | onSubmit = (err, values) => { 23 | console.log('value collected ->', { ...values, autoLogin: this.state.autoLogin }); 24 | if (this.state.type === 'tab1') { 25 | this.setState({ 26 | notice: '', 27 | }, () => { 28 | if (!err && (values.username !== 'admin' || values.password !== '888888')) { 29 | setTimeout(() => { 30 | this.setState({ 31 | notice: 'The combination of username and password is incorrect!', 32 | }); 33 | }, 500); 34 | } 35 | }); 36 | } 37 | } 38 | onTabChange = (key) => { 39 | this.setState({ 40 | type: key, 41 | }); 42 | } 43 | changeAutoLogin = (e) => { 44 | this.setState({ 45 | autoLogin: e.target.checked, 46 | }); 47 | } 48 | render() { 49 | return ( 50 | 55 | 56 | { 57 | this.state.notice && 58 | 59 | } 60 | 61 | 62 | 63 | 64 | 65 | console.log('Get captcha!')} name="captcha" /> 66 | 67 |
68 | Keep me logged in 69 | Forgot password 70 |
71 | Login 72 |
73 | Other login methods 74 | 75 | 76 | 77 | Register 78 |
79 |
80 | ); 81 | } 82 | } 83 | 84 | ReactDOM.render(, mountNode); 85 | ```` 86 | 87 | 116 | -------------------------------------------------------------------------------- /src/pages/List/BasicList.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .standardList { 5 | :global { 6 | .ant-card-head { 7 | border-bottom: none; 8 | } 9 | .ant-card-head-title { 10 | line-height: 32px; 11 | padding: 24px 0; 12 | } 13 | .ant-card-extra { 14 | padding: 24px 0; 15 | } 16 | .ant-list-pagination { 17 | text-align: right; 18 | margin-top: 24px; 19 | } 20 | .ant-avatar-lg { 21 | width: 48px; 22 | height: 48px; 23 | line-height: 48px; 24 | } 25 | } 26 | .headerInfo { 27 | position: relative; 28 | text-align: center; 29 | & > span { 30 | color: @text-color-secondary; 31 | display: inline-block; 32 | font-size: @font-size-base; 33 | line-height: 22px; 34 | margin-bottom: 4px; 35 | } 36 | & > p { 37 | color: @heading-color; 38 | font-size: 24px; 39 | line-height: 32px; 40 | margin: 0; 41 | } 42 | & > em { 43 | background-color: @border-color-split; 44 | position: absolute; 45 | height: 56px; 46 | width: 1px; 47 | top: 0; 48 | right: 0; 49 | } 50 | } 51 | .listContent { 52 | font-size: 0; 53 | .listContentItem { 54 | color: @text-color-secondary; 55 | display: inline-block; 56 | vertical-align: middle; 57 | font-size: @font-size-base; 58 | margin-left: 40px; 59 | > span { 60 | line-height: 20px; 61 | } 62 | > p { 63 | margin-top: 4px; 64 | margin-bottom: 0; 65 | line-height: 22px; 66 | } 67 | } 68 | } 69 | .extraContentSearch { 70 | margin-left: 16px; 71 | width: 272px; 72 | } 73 | } 74 | 75 | @media screen and (max-width: @screen-xs) { 76 | .standardList { 77 | :global { 78 | .ant-list-item-content { 79 | display: block; 80 | flex: none; 81 | width: 100%; 82 | } 83 | .ant-list-item-action { 84 | margin-left: 0; 85 | } 86 | } 87 | .listContent { 88 | margin-left: 0; 89 | & > div { 90 | margin-left: 0; 91 | } 92 | } 93 | .listCard { 94 | :global { 95 | .ant-card-head-title { 96 | overflow: visible; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | @media screen and (max-width: @screen-sm) { 104 | .standardList { 105 | .extraContentSearch { 106 | margin-left: 0; 107 | width: 100%; 108 | } 109 | .headerInfo { 110 | margin-bottom: 16px; 111 | & > em { 112 | display: none; 113 | } 114 | } 115 | } 116 | } 117 | 118 | @media screen and (max-width: @screen-md) { 119 | .standardList { 120 | .listContent { 121 | & > div { 122 | display: block; 123 | } 124 | & > div:last-child { 125 | top: 0; 126 | width: 100%; 127 | } 128 | } 129 | } 130 | .listCard { 131 | :global { 132 | .ant-radio-group { 133 | display: block; 134 | margin-bottom: 8px; 135 | } 136 | } 137 | } 138 | } 139 | 140 | @media screen and (max-width: @screen-lg) and (min-width: @screen-md) { 141 | .standardList { 142 | .listContent { 143 | & > div { 144 | display: block; 145 | } 146 | & > div:last-child { 147 | top: 0; 148 | width: 100%; 149 | } 150 | } 151 | } 152 | } 153 | 154 | @media screen and (max-width: @screen-xl) { 155 | .standardList { 156 | .listContent { 157 | & > div { 158 | margin-left: 24px; 159 | } 160 | & > div:last-child { 161 | top: 0; 162 | } 163 | } 164 | } 165 | } 166 | 167 | @media screen and (max-width: 1400px) { 168 | .standardList { 169 | .listContent { 170 | text-align: right; 171 | & > div:last-child { 172 | top: 0; 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /mock/chart.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | // mock data 4 | const visitData = [] 5 | const beginDay = new Date().getTime() 6 | 7 | const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5] 8 | for (let i = 0; i < fakeY.length; i += 1) { 9 | visitData.push({ 10 | x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), 11 | y: fakeY[i], 12 | }) 13 | } 14 | 15 | const visitData2 = [] 16 | const fakeY2 = [1, 6, 4, 8, 3, 7, 2] 17 | for (let i = 0; i < fakeY2.length; i += 1) { 18 | visitData2.push({ 19 | x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), 20 | y: fakeY2[i], 21 | }) 22 | } 23 | 24 | const salesData = [] 25 | for (let i = 0; i < 12; i += 1) { 26 | salesData.push({ 27 | x: `${i + 1}月`, 28 | y: Math.floor(Math.random() * 1000) + 200, 29 | }) 30 | } 31 | const searchData = [] 32 | for (let i = 0; i < 50; i += 1) { 33 | searchData.push({ 34 | index: i + 1, 35 | keyword: `搜索关键词-${i}`, 36 | count: Math.floor(Math.random() * 1000), 37 | range: Math.floor(Math.random() * 100), 38 | status: Math.floor((Math.random() * 10) % 2), 39 | }) 40 | } 41 | const salesTypeData = [ 42 | { 43 | x: '家用电器', 44 | y: 4544, 45 | }, 46 | { 47 | x: '食用酒水', 48 | y: 3321, 49 | }, 50 | { 51 | x: '个护健康', 52 | y: 3113, 53 | }, 54 | { 55 | x: '服饰箱包', 56 | y: 2341, 57 | }, 58 | { 59 | x: '母婴产品', 60 | y: 1231, 61 | }, 62 | { 63 | x: '其他', 64 | y: 1231, 65 | }, 66 | ] 67 | 68 | const salesTypeDataOnline = [ 69 | { 70 | x: '家用电器', 71 | y: 244, 72 | }, 73 | { 74 | x: '食用酒水', 75 | y: 321, 76 | }, 77 | { 78 | x: '个护健康', 79 | y: 311, 80 | }, 81 | { 82 | x: '服饰箱包', 83 | y: 41, 84 | }, 85 | { 86 | x: '母婴产品', 87 | y: 121, 88 | }, 89 | { 90 | x: '其他', 91 | y: 111, 92 | }, 93 | ] 94 | 95 | const salesTypeDataOffline = [ 96 | { 97 | x: '家用电器', 98 | y: 99, 99 | }, 100 | { 101 | x: '个护健康', 102 | y: 188, 103 | }, 104 | { 105 | x: '服饰箱包', 106 | y: 344, 107 | }, 108 | { 109 | x: '母婴产品', 110 | y: 255, 111 | }, 112 | { 113 | x: '其他', 114 | y: 65, 115 | }, 116 | ] 117 | 118 | const offlineData = [] 119 | for (let i = 0; i < 10; i += 1) { 120 | offlineData.push({ 121 | name: `门店${i}`, 122 | cvr: Math.ceil(Math.random() * 9) / 10, 123 | }) 124 | } 125 | const offlineChartData = [] 126 | for (let i = 0; i < 20; i += 1) { 127 | offlineChartData.push({ 128 | x: new Date().getTime() + 1000 * 60 * 30 * i, 129 | y1: Math.floor(Math.random() * 100) + 10, 130 | y2: Math.floor(Math.random() * 100) + 10, 131 | }) 132 | } 133 | 134 | const radarOriginData = [ 135 | { 136 | name: '个人', 137 | ref: 10, 138 | koubei: 8, 139 | output: 4, 140 | contribute: 5, 141 | hot: 7, 142 | }, 143 | { 144 | name: '团队', 145 | ref: 3, 146 | koubei: 9, 147 | output: 6, 148 | contribute: 3, 149 | hot: 1, 150 | }, 151 | { 152 | name: '部门', 153 | ref: 4, 154 | koubei: 1, 155 | output: 6, 156 | contribute: 5, 157 | hot: 7, 158 | }, 159 | ] 160 | 161 | // 162 | const radarData = [] 163 | const radarTitleMap = { 164 | ref: '引用', 165 | koubei: '口碑', 166 | output: '产量', 167 | contribute: '贡献', 168 | hot: '热度', 169 | } 170 | radarOriginData.forEach(item => { 171 | Object.keys(item).forEach(key => { 172 | if (key !== 'name') { 173 | radarData.push({ 174 | name: item.name, 175 | label: radarTitleMap[key], 176 | value: item[key], 177 | }) 178 | } 179 | }) 180 | }) 181 | 182 | export const getFakeChartData = { 183 | visitData, 184 | visitData2, 185 | salesData, 186 | searchData, 187 | offlineData, 188 | offlineChartData, 189 | salesTypeData, 190 | salesTypeDataOnline, 191 | salesTypeDataOffline, 192 | radarData, 193 | } 194 | 195 | export default { 196 | 'GET /api/fake_chart_data': getFakeChartData, 197 | } 198 | -------------------------------------------------------------------------------- /src/pages/Forms/StepForm/Step1.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import { connect } from 'dva' 3 | import { Form, Input, Button, Select, Divider } from 'antd' 4 | import { routerRedux } from 'dva/router' 5 | import styles from './style.less' 6 | 7 | const { Option } = Select 8 | 9 | const formItemLayout = { 10 | labelCol: { 11 | span: 5, 12 | }, 13 | wrapperCol: { 14 | span: 19, 15 | }, 16 | } 17 | 18 | @Form.create() 19 | class Step1 extends React.PureComponent { 20 | render() { 21 | const { form, dispatch, data } = this.props 22 | const { getFieldDecorator, validateFields } = form 23 | const onValidateForm = () => { 24 | validateFields((err, values) => { 25 | if (!err) { 26 | dispatch({ 27 | type: 'form/saveStepFormData', 28 | payload: values, 29 | }) 30 | dispatch(routerRedux.push('/form/step-form/confirm')) 31 | } 32 | }) 33 | } 34 | return ( 35 | 36 |
37 | 38 | {getFieldDecorator('payAccount', { 39 | initialValue: data.payAccount, 40 | rules: [{ required: true, message: '请选择付款账户' }], 41 | })( 42 | 45 | )} 46 | 47 | 48 | 49 | 53 | {getFieldDecorator('receiverAccount', { 54 | initialValue: data.receiverAccount, 55 | rules: [ 56 | { required: true, message: '请输入收款人账户' }, 57 | { type: 'email', message: '账户名应为邮箱格式' }, 58 | ], 59 | })()} 60 | 61 | 62 | 63 | {getFieldDecorator('receiverName', { 64 | initialValue: data.receiverName, 65 | rules: [{ required: true, message: '请输入收款人姓名' }], 66 | })()} 67 | 68 | 69 | {getFieldDecorator('amount', { 70 | initialValue: data.amount, 71 | rules: [ 72 | { required: true, message: '请输入转账金额' }, 73 | { 74 | pattern: /^(\d+)((?:\.\d+)?)$/, 75 | message: '请输入合法金额数字', 76 | }, 77 | ], 78 | })()} 79 | 80 | 90 | 93 | 94 | 95 | 96 |
97 |

说明

98 |

转账到支付宝账户

99 |

100 | 如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。 101 |

102 |

转账到银行卡

103 |

104 | 如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。 105 |

106 |
107 |
108 | ) 109 | } 110 | } 111 | 112 | export default connect(({ form }) => ({ 113 | data: form.step, 114 | }))(Step1) 115 | -------------------------------------------------------------------------------- /mock/rule.js: -------------------------------------------------------------------------------- 1 | import { parse } from 'url' 2 | 3 | // mock tableListDataSource 4 | let tableListDataSource = [] 5 | for (let i = 0; i < 46; i += 1) { 6 | tableListDataSource.push({ 7 | key: i, 8 | disabled: i % 6 === 0, 9 | href: 'https://ant.design', 10 | avatar: [ 11 | 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 12 | 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', 13 | ][i % 2], 14 | no: `TradeCode ${i}`, 15 | title: `一个任务名称 ${i}`, 16 | owner: '曲丽丽', 17 | description: '这是一段描述', 18 | callNo: Math.floor(Math.random() * 1000), 19 | status: Math.floor(Math.random() * 10) % 4, 20 | updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`), 21 | createdAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`), 22 | progress: Math.ceil(Math.random() * 100), 23 | }) 24 | } 25 | 26 | export function getRule(req, res, u) { 27 | let url = u 28 | if (!url || Object.prototype.toString.call(url) !== '[object String]') { 29 | url = req.url; // eslint-disable-line 30 | } 31 | 32 | const params = parse(url, true).query 33 | 34 | let dataSource = [...tableListDataSource] 35 | 36 | if (params.sorter) { 37 | const s = params.sorter.split('_') 38 | dataSource = dataSource.sort((prev, next) => { 39 | if (s[1] === 'descend') { 40 | return next[s[0]] - prev[s[0]] 41 | } 42 | return prev[s[0]] - next[s[0]] 43 | }) 44 | } 45 | 46 | if (params.status) { 47 | const status = params.status.split(',') 48 | let filterDataSource = [] 49 | status.forEach(s => { 50 | filterDataSource = filterDataSource.concat( 51 | [...dataSource].filter(data => parseInt(data.status, 10) === parseInt(s[0], 10)) 52 | ) 53 | }) 54 | dataSource = filterDataSource 55 | } 56 | 57 | if (params.no) { 58 | dataSource = dataSource.filter(data => data.no.indexOf(params.no) > -1) 59 | } 60 | 61 | let pageSize = 10 62 | if (params.pageSize) { 63 | pageSize = params.pageSize * 1 64 | } 65 | 66 | const result = { 67 | list: dataSource, 68 | pagination: { 69 | total: dataSource.length, 70 | pageSize, 71 | current: parseInt(params.currentPage, 10) || 1, 72 | }, 73 | } 74 | 75 | if (res && res.json) { 76 | res.json(result) 77 | } else { 78 | return result 79 | } 80 | } 81 | 82 | export function postRule(req, res, u, b) { 83 | let url = u 84 | if (!url || Object.prototype.toString.call(url) !== '[object String]') { 85 | url = req.url; // eslint-disable-line 86 | } 87 | 88 | const body = (b && b.body) || req.body 89 | const { method, no, description } = body 90 | 91 | switch (method) { 92 | /* eslint no-case-declarations:0 */ 93 | case 'delete': 94 | tableListDataSource = tableListDataSource.filter(item => no.indexOf(item.no) === -1) 95 | break 96 | case 'post': 97 | const i = Math.ceil(Math.random() * 10000) 98 | tableListDataSource.unshift({ 99 | key: i, 100 | href: 'https://ant.design', 101 | avatar: [ 102 | 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 103 | 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png', 104 | ][i % 2], 105 | no: `TradeCode ${i}`, 106 | title: `一个任务名称 ${i}`, 107 | owner: '曲丽丽', 108 | description, 109 | callNo: Math.floor(Math.random() * 1000), 110 | status: Math.floor(Math.random() * 10) % 2, 111 | updatedAt: new Date(), 112 | createdAt: new Date(), 113 | progress: Math.ceil(Math.random() * 100), 114 | }) 115 | break 116 | default: 117 | break 118 | } 119 | 120 | const result = { 121 | list: tableListDataSource, 122 | pagination: { 123 | total: tableListDataSource.length, 124 | }, 125 | } 126 | 127 | if (res && res.json) { 128 | res.json(result) 129 | } else { 130 | return result 131 | } 132 | } 133 | 134 | export default { 135 | 'GET /api/rule': getRule, 136 | 'POST /api/rule': { 137 | $params: { 138 | pageSize: { 139 | desc: '分页', 140 | exp: 2, 141 | }, 142 | }, 143 | $body: postRule, 144 | }, 145 | } 146 | -------------------------------------------------------------------------------- /public/exImages/MjEImQtenlyueSmVEfUD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 4 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/exImages/wAhyIChODzsoKIOBHcBk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bells (1) 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/common/nav/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Route from 'src/components/Route' 3 | 4 | export default ( 5 | import('src/layouts/BasicLayout')} 12 | > 13 | 14 | import('src/pages/Dashboard/Analysis')} 19 | /> 20 | import('src/pages/Dashboard/Monitor')} 25 | /> 26 | import('src/pages/Dashboard/Workplace')} 31 | /> 32 | 33 | 34 | import('src/pages/Forms/BasicForm')} 39 | /> 40 | import('src/pages/Forms/StepForm')} 45 | > 46 | import('src/pages/Forms/StepForm/Step1')} 50 | /> 51 | import('src/pages/Forms/StepForm/Step2')} 55 | /> 56 | import('src/pages/Forms/StepForm/Step3')} 60 | /> 61 | 62 | import('src/pages/Forms/AdvancedForm')} 67 | /> 68 | 69 | 70 | import('src/pages/List/TableList')} 75 | /> 76 | import('src/pages/List/BasicList')} 81 | /> 82 | import('src/pages/List/CardList')} 87 | /> 88 | import('src/pages/List/List')}> 89 | import('src/pages/List/Articles')} 94 | /> 95 | import('src/pages/List/Projects')} 100 | /> 101 | import('src/pages/List/Applications')} 106 | /> 107 | 108 | 109 | 110 | import('src/pages/Profile/BasicProfile')} 115 | /> 116 | import('src/pages/Profile/AdvancedProfile')} 121 | /> 122 | 123 | 124 | import('src/pages/Result/Success')} /> 125 | import('src/pages/Result/Error')} /> 126 | 127 | 128 | import('src/pages/Exception/403')} /> 129 | import('src/pages/Exception/404')} /> 130 | import('src/pages/Exception/500')} /> 131 | import('src/pages/Exception/triggerException')} 137 | /> 138 | 139 | 140 | ) 141 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 28 Copy 5 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 42 | 43 | -------------------------------------------------------------------------------- /src/pages/List/BasicList.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import moment from 'moment' 3 | import { connect } from 'dva' 4 | import { 5 | List, 6 | Card, 7 | Row, 8 | Col, 9 | Radio, 10 | Input, 11 | Progress, 12 | Button, 13 | Icon, 14 | Dropdown, 15 | Menu, 16 | Avatar, 17 | } from 'antd' 18 | 19 | import PageHeaderLayout from '../../layouts/PageHeaderLayout' 20 | 21 | import styles from './BasicList.less' 22 | 23 | const RadioButton = Radio.Button 24 | const RadioGroup = Radio.Group 25 | const { Search } = Input 26 | 27 | @connect(({ list, loading }) => ({ 28 | list, 29 | loading: loading.models.list, 30 | })) 31 | export default class BasicList extends PureComponent { 32 | componentDidMount() { 33 | this.props.dispatch({ 34 | type: 'list/fetch', 35 | payload: { 36 | count: 5, 37 | }, 38 | }) 39 | } 40 | 41 | render() { 42 | const { list: { list }, loading } = this.props 43 | 44 | const Info = ({ title, value, bordered }) => ( 45 |
46 | {title} 47 |

{value}

48 | {bordered && } 49 |
50 | ) 51 | 52 | const extraContent = ( 53 |
54 | 55 | 全部 56 | 进行中 57 | 等待中 58 | 59 | ({})} /> 60 |
61 | ) 62 | 63 | const paginationProps = { 64 | showSizeChanger: true, 65 | showQuickJumper: true, 66 | pageSize: 5, 67 | total: 50, 68 | } 69 | 70 | const ListContent = ({ data: { owner, createdAt, percent, status } }) => ( 71 |
72 |
73 | Owner 74 |

{owner}

75 |
76 |
77 | 开始时间 78 |

{moment(createdAt).format('YYYY-MM-DD HH:mm')}

79 |
80 |
81 | 82 |
83 |
84 | ) 85 | 86 | const menu = ( 87 | 88 | 89 | 编辑 90 | 91 | 92 | 删除 93 | 94 | 95 | ) 96 | 97 | const MoreBtn = () => ( 98 | 99 | 100 | 更多 101 | 102 | 103 | ) 104 | 105 | return ( 106 | 107 |
108 | 109 | 110 |
111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 130 | 133 | ( 140 | 编辑, ]}> 141 | } 143 | title={{item.title}} 144 | description={item.subDescription} 145 | /> 146 | 147 | 148 | )} 149 | /> 150 | 151 | 152 | 153 | ) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export function fixedZero(val) { 4 | return val * 1 < 10 ? `0${val}` : val 5 | } 6 | 7 | export function getTimeDistance(type) { 8 | const now = new Date() 9 | const oneDay = 1000 * 60 * 60 * 24 10 | 11 | if (type === 'today') { 12 | now.setHours(0) 13 | now.setMinutes(0) 14 | now.setSeconds(0) 15 | return [moment(now), moment(now.getTime() + (oneDay - 1000))] 16 | } 17 | 18 | if (type === 'week') { 19 | let day = now.getDay() 20 | now.setHours(0) 21 | now.setMinutes(0) 22 | now.setSeconds(0) 23 | 24 | if (day === 0) { 25 | day = 6 26 | } else { 27 | day -= 1 28 | } 29 | 30 | const beginTime = now.getTime() - day * oneDay 31 | 32 | return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))] 33 | } 34 | 35 | if (type === 'month') { 36 | const year = now.getFullYear() 37 | const month = now.getMonth() 38 | const nextDate = moment(now).add(1, 'months') 39 | const nextYear = nextDate.year() 40 | const nextMonth = nextDate.month() 41 | 42 | return [ 43 | moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`), 44 | moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000), 45 | ] 46 | } 47 | 48 | if (type === 'year') { 49 | const year = now.getFullYear() 50 | 51 | return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)] 52 | } 53 | } 54 | 55 | export function getPlainNode(nodeList, parentPath = '') { 56 | const arr = [] 57 | nodeList.forEach(node => { 58 | const item = node 59 | item.path = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/') 60 | item.exact = true 61 | if (item.children && !item.component) { 62 | arr.push(...getPlainNode(item.children, item.path)) 63 | } else { 64 | if (item.children && item.component) { 65 | item.exact = false 66 | } 67 | arr.push(item) 68 | } 69 | }) 70 | return arr 71 | } 72 | 73 | export function digitUppercase(n) { 74 | const fraction = ['角', '分'] 75 | const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] 76 | const unit = [['元', '万', '亿'], ['', '拾', '佰', '仟']] 77 | let num = Math.abs(n) 78 | let s = '' 79 | fraction.forEach((item, index) => { 80 | s += (digit[Math.floor(num * 10 * 10 ** index) % 10] + item).replace(/零./, '') 81 | }) 82 | s = s || '整' 83 | num = Math.floor(num) 84 | for (let i = 0; i < unit[0].length && num > 0; i += 1) { 85 | let p = '' 86 | for (let j = 0; j < unit[1].length && num > 0; j += 1) { 87 | p = digit[num % 10] + unit[1][j] + p 88 | num = Math.floor(num / 10) 89 | } 90 | s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s 91 | } 92 | 93 | return s 94 | .replace(/(零.)*零元/, '元') 95 | .replace(/(零.)+/g, '零') 96 | .replace(/^整$/, '零元整') 97 | } 98 | 99 | function getRelation(str1, str2) { 100 | if (str1 === str2) { 101 | console.warn('Two path are equal!') // eslint-disable-line 102 | } 103 | const arr1 = str1.split('/') 104 | const arr2 = str2.split('/') 105 | if (arr2.every((item, index) => item === arr1[index])) { 106 | return 1 107 | } else if (arr1.every((item, index) => item === arr2[index])) { 108 | return 2 109 | } 110 | return 3 111 | } 112 | 113 | function getRenderArr(routes) { 114 | let renderArr = [] 115 | renderArr.push(routes[0]) 116 | for (let i = 1; i < routes.length; i += 1) { 117 | let isAdd = false 118 | // 是否包含 119 | isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3) 120 | // 去重 121 | renderArr = renderArr.filter(item => getRelation(item, routes[i]) !== 1) 122 | if (isAdd) { 123 | renderArr.push(routes[i]) 124 | } 125 | } 126 | return renderArr 127 | } 128 | 129 | /** 130 | * Get router routing configuration 131 | * { path:{name,...param}}=>Array<{name,path ...param}> 132 | * @param {string} path 133 | * @param {routerData} routerData 134 | */ 135 | export function getRoutes(path, routerData) { 136 | let routes = Object.keys(routerData).filter( 137 | routePath => routePath.indexOf(path) === 0 && routePath !== path 138 | ) 139 | // Replace path to '' eg. path='user' /user/name => name 140 | routes = routes.map(item => item.replace(path, '')) 141 | // Get the route to be rendered to remove the deep rendering 142 | const renderArr = getRenderArr(routes) 143 | // Conversion and stitching parameters 144 | const renderRoutes = renderArr.map(item => { 145 | const exact = !routes.some(route => route !== item && getRelation(route, item) === 1) 146 | return { 147 | exact, 148 | ...routerData[`${path}${item}`], 149 | key: `${path}${item}`, 150 | path: `${path}${item}`, 151 | } 152 | }) 153 | return renderRoutes 154 | } 155 | 156 | /* eslint no-useless-escape:0 */ 157 | const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/g 158 | 159 | export function isUrl(path) { 160 | return reg.test(path) 161 | } 162 | -------------------------------------------------------------------------------- /src/pages/Dashboard/Workplace.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @import '../../utils/utils.less'; 3 | 4 | .activitiesList { 5 | padding: 0 24px 8px 24px; 6 | .username { 7 | color: @text-color; 8 | } 9 | .event { 10 | font-weight: normal; 11 | } 12 | } 13 | 14 | .pageHeaderContent { 15 | display: flex; 16 | .avatar { 17 | flex: 0 1 72px; 18 | margin-bottom: 8px; 19 | & > span { 20 | border-radius: 72px; 21 | display: block; 22 | width: 72px; 23 | height: 72px; 24 | } 25 | } 26 | .content { 27 | position: relative; 28 | top: 4px; 29 | margin-left: 24px; 30 | flex: 1 1 auto; 31 | color: @text-color-secondary; 32 | line-height: 22px; 33 | .contentTitle { 34 | font-size: 20px; 35 | line-height: 28px; 36 | font-weight: 500; 37 | color: @heading-color; 38 | margin-bottom: 12px; 39 | } 40 | } 41 | } 42 | 43 | .extraContent { 44 | .clearfix(); 45 | float: right; 46 | white-space: nowrap; 47 | .statItem { 48 | padding: 0 32px; 49 | position: relative; 50 | display: inline-block; 51 | > p:first-child { 52 | color: @text-color-secondary; 53 | font-size: @font-size-base; 54 | line-height: 22px; 55 | margin-bottom: 4px; 56 | } 57 | > p { 58 | color: @heading-color; 59 | font-size: 30px; 60 | line-height: 38px; 61 | margin: 0; 62 | > span { 63 | color: @text-color-secondary; 64 | font-size: 20px; 65 | } 66 | } 67 | &:after { 68 | background-color: @border-color-split; 69 | position: absolute; 70 | top: 8px; 71 | right: 0; 72 | width: 1px; 73 | height: 40px; 74 | content: ''; 75 | } 76 | &:last-child { 77 | padding-right: 0; 78 | &:after { 79 | display: none; 80 | } 81 | } 82 | } 83 | } 84 | 85 | .members { 86 | a { 87 | display: block; 88 | margin: 12px 0; 89 | line-height: 24px; 90 | height: 24px; 91 | .textOverflow(); 92 | .member { 93 | font-size: @font-size-base; 94 | color: @text-color; 95 | line-height: 24px; 96 | max-width: 100px; 97 | vertical-align: top; 98 | margin-left: 12px; 99 | transition: all 0.3s; 100 | display: inline-block; 101 | .textOverflow(); 102 | } 103 | &:hover { 104 | span { 105 | color: @primary-color; 106 | } 107 | } 108 | } 109 | } 110 | 111 | .projectList { 112 | :global { 113 | .ant-card-meta-description { 114 | color: @text-color-secondary; 115 | height: 44px; 116 | line-height: 22px; 117 | overflow: hidden; 118 | } 119 | } 120 | .cardTitle { 121 | font-size: 0; 122 | a { 123 | color: @heading-color; 124 | margin-left: 12px; 125 | line-height: 24px; 126 | height: 24px; 127 | display: inline-block; 128 | vertical-align: top; 129 | font-size: @font-size-base; 130 | &:hover { 131 | color: @primary-color; 132 | } 133 | } 134 | } 135 | .projectGrid { 136 | width: 33.33%; 137 | } 138 | .projectItemContent { 139 | display: flex; 140 | margin-top: 8px; 141 | overflow: hidden; 142 | font-size: 12px; 143 | height: 20px; 144 | line-height: 20px; 145 | .textOverflow(); 146 | a { 147 | color: @text-color-secondary; 148 | display: inline-block; 149 | flex: 1 1 0; 150 | .textOverflow(); 151 | &:hover { 152 | color: @primary-color; 153 | } 154 | } 155 | .datetime { 156 | color: @disabled-color; 157 | flex: 0 0 auto; 158 | float: right; 159 | } 160 | } 161 | } 162 | 163 | .datetime { 164 | color: @disabled-color; 165 | } 166 | 167 | @media screen and (max-width: @screen-xl) and (min-width: @screen-lg) { 168 | .activeCard { 169 | margin-bottom: 24px; 170 | } 171 | .members { 172 | margin-bottom: 0; 173 | } 174 | .extraContent { 175 | margin-left: -44px; 176 | .statItem { 177 | padding: 0 16px; 178 | } 179 | } 180 | } 181 | 182 | @media screen and (max-width: @screen-lg) { 183 | .activeCard { 184 | margin-bottom: 24px; 185 | } 186 | .members { 187 | margin-bottom: 0; 188 | } 189 | .extraContent { 190 | float: none; 191 | margin-right: 0; 192 | .statItem { 193 | padding: 0 16px; 194 | text-align: left; 195 | &:after { 196 | display: none; 197 | } 198 | } 199 | } 200 | } 201 | 202 | @media screen and (max-width: @screen-md) { 203 | .extraContent { 204 | margin-left: -16px; 205 | } 206 | .projectList { 207 | .projectGrid { 208 | width: 50%; 209 | } 210 | } 211 | } 212 | 213 | @media screen and (max-width: @screen-sm) { 214 | .pageHeaderContent { 215 | display: block; 216 | .content { 217 | margin-left: 0; 218 | } 219 | } 220 | .extraContent { 221 | .statItem { 222 | float: none; 223 | } 224 | } 225 | } 226 | 227 | @media screen and (max-width: @screen-xs) { 228 | .projectList { 229 | .projectGrid { 230 | width: 100%; 231 | } 232 | } 233 | } 234 | --------------------------------------------------------------------------------