├── .browserslistrc ├── .editorconfig ├── .env ├── .env.development ├── .env.preview ├── .eslintrc.js ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── need-help-issue.md └── pull_request_template.md ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .lintstagedrc.json ├── .prettierrc ├── .stylelintrc.js ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── README.zh-CN.md ├── babel.config.js ├── commitlint.config.js ├── config ├── plugin.config.js └── themePluginConfig.js ├── deploy ├── caddy.conf └── nginx.conf ├── docs ├── add-page-loading-animate.md └── webpack-bundle-analyzer.md ├── jest.config.js ├── jsconfig.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── avatar2.jpg ├── index.html └── logo.png ├── src ├── App.vue ├── api │ ├── login.js │ └── manage.js ├── assets │ ├── background.svg │ ├── icons │ │ └── bx-analyse.svg │ └── logo.svg ├── components │ ├── ArticleListContent │ │ ├── ArticleListContent.vue │ │ └── index.js │ ├── AvatarList │ │ ├── Item.jsx │ │ ├── List.jsx │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Charts │ │ ├── Bar.vue │ │ ├── ChartCard.vue │ │ ├── Liquid.vue │ │ ├── MiniArea.vue │ │ ├── MiniBar.vue │ │ ├── MiniProgress.vue │ │ ├── MiniSmoothArea.vue │ │ ├── Radar.vue │ │ ├── RankList.vue │ │ ├── TagCloud.vue │ │ ├── TransferBar.vue │ │ ├── Trend.vue │ │ ├── chart.less │ │ └── smooth.area.less │ ├── Dialog.js │ ├── Editor │ │ ├── QuillEditor.vue │ │ └── WangEditor.vue │ ├── Ellipsis │ │ ├── Ellipsis.vue │ │ ├── index.js │ │ └── index.md │ ├── FooterToolbar │ │ ├── FooterToolBar.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── GlobalFooter │ │ └── index.vue │ ├── GlobalHeader │ │ ├── AvatarDropdown.vue │ │ └── RightContent.vue │ ├── IconSelector │ │ ├── IconSelector.vue │ │ ├── README.md │ │ ├── icons.js │ │ └── index.js │ ├── MultiTab │ │ ├── MultiTab.vue │ │ ├── events.js │ │ ├── index.js │ │ └── index.less │ ├── NProgress │ │ └── nprogress.less │ ├── NoticeIcon │ │ ├── NoticeIcon.vue │ │ └── index.js │ ├── NumberInfo │ │ ├── NumberInfo.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── Other │ │ └── CarbonAds.vue │ ├── PageLoading │ │ └── index.jsx │ ├── Search │ │ ├── GlobalSearch.jsx │ │ └── index.less │ ├── SelectLang │ │ ├── index.jsx │ │ └── index.less │ ├── SettingDrawer │ │ ├── SettingDrawer.vue │ │ ├── SettingItem.vue │ │ ├── index.js │ │ ├── settingConfig.js │ │ └── themeColor.js │ ├── StandardFormRow │ │ ├── StandardFormRow.vue │ │ └── index.js │ ├── Table │ │ ├── README.md │ │ └── index.js │ ├── TagSelect │ │ ├── TagSelectOption.jsx │ │ └── index.jsx │ ├── TextArea │ │ ├── index.jsx │ │ └── style.less │ ├── Tree │ │ └── Tree.jsx │ ├── Trend │ │ ├── Trend.vue │ │ ├── index.js │ │ ├── index.less │ │ └── index.md │ ├── _util │ │ └── util.js │ ├── index.js │ ├── index.less │ └── tools │ │ └── TwoStepCaptcha.vue ├── config │ ├── defaultSettings.js │ └── router.config.js ├── core │ ├── bootstrap.js │ ├── directives │ │ └── action.js │ ├── icons.js │ ├── lazy_use.js │ ├── permission │ │ └── permission.js │ └── use.js ├── global.less ├── layouts │ ├── BasicLayout.less │ ├── BasicLayout.vue │ ├── BlankLayout.vue │ ├── PageView.vue │ ├── RouteView.vue │ ├── UserLayout.vue │ └── index.js ├── locales │ ├── index.js │ └── lang │ │ ├── en-US.js │ │ ├── en-US │ │ ├── account.js │ │ ├── account │ │ │ └── settings.js │ │ ├── dashboard.js │ │ ├── dashboard │ │ │ └── analysis.js │ │ ├── form.js │ │ ├── form │ │ │ └── basicForm.js │ │ ├── global.js │ │ ├── menu.js │ │ ├── result.js │ │ ├── result │ │ │ ├── fail.js │ │ │ └── success.js │ │ ├── setting.js │ │ └── user.js │ │ ├── zh-CN.js │ │ └── zh-CN │ │ ├── account.js │ │ ├── account │ │ └── settings.js │ │ ├── dashboard.js │ │ ├── dashboard │ │ └── analysis.js │ │ ├── form.js │ │ ├── form │ │ └── basicForm.js │ │ ├── global.js │ │ ├── menu.js │ │ ├── result.js │ │ ├── result │ │ ├── fail.js │ │ └── success.js │ │ ├── setting.js │ │ └── user.js ├── main.js ├── mock │ ├── index.js │ ├── services │ │ ├── article.js │ │ ├── auth.js │ │ ├── manage.js │ │ ├── other.js │ │ ├── tagCloud.js │ │ └── user.js │ └── util.js ├── permission.js ├── router │ ├── README.md │ ├── generator-routers.js │ └── index.js ├── store │ ├── app-mixin.js │ ├── device-mixin.js │ ├── getters.js │ ├── i18n-mixin.js │ ├── index.js │ ├── modules │ │ ├── app.js │ │ ├── async-router.js │ │ ├── static-router.js │ │ └── user.js │ └── mutation-types.js ├── utils │ ├── axios.js │ ├── domUtil.js │ ├── filter.js │ ├── request.js │ ├── routeConvert.js │ ├── screenLog.js │ ├── util.js │ └── utils.less └── views │ ├── 404.vue │ ├── account │ ├── center │ │ ├── index.vue │ │ └── page │ │ │ ├── App.vue │ │ │ ├── Article.vue │ │ │ ├── Project.vue │ │ │ └── index.js │ └── settings │ │ ├── AvatarModal.vue │ │ ├── BasicSetting.vue │ │ ├── Binding.vue │ │ ├── Custom.vue │ │ ├── Index.vue │ │ ├── Notification.vue │ │ └── Security.vue │ ├── dashboard │ ├── Analysis.vue │ ├── Monitor.vue │ ├── TestWork.vue │ ├── Workplace.less │ └── Workplace.vue │ ├── exception │ ├── 403.vue │ ├── 404.vue │ └── 500.vue │ ├── form │ ├── advancedForm │ │ ├── AdvancedForm.vue │ │ ├── RepositoryForm.vue │ │ └── TaskForm.vue │ ├── basicForm │ │ └── index.vue │ └── stepForm │ │ ├── Step1.vue │ │ ├── Step2.vue │ │ ├── Step3.vue │ │ └── StepForm.vue │ ├── list │ ├── BasicList.vue │ ├── CardList.vue │ ├── QueryList.vue │ ├── TableList.vue │ ├── components │ │ └── Info.vue │ ├── modules │ │ ├── CreateForm.vue │ │ ├── StepByStepModal.vue │ │ └── TaskForm.vue │ ├── search │ │ ├── Applications.vue │ │ ├── Article.vue │ │ ├── Projects.vue │ │ ├── SearchLayout.vue │ │ └── components │ │ │ ├── CardInfo.vue │ │ │ └── IconText.vue │ └── table │ │ ├── Edit.vue │ │ └── List.vue │ ├── other │ ├── BigForm.vue │ ├── IconSelectorView.vue │ ├── PermissionList.vue │ ├── RoleList.vue │ ├── TableInnerEditList.vue │ ├── TreeList.vue │ ├── UserList.vue │ └── modules │ │ ├── OrgModal.vue │ │ └── RoleModal.vue │ ├── profile │ ├── advanced │ │ └── Advanced.vue │ └── basic │ │ └── index.vue │ ├── result │ ├── Error.vue │ └── Success.vue │ └── user │ ├── Login.vue │ ├── Register.vue │ └── RegisterResult.vue ├── tests └── unit │ └── .eslintrc.js └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 10 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=false 5 | indent_style=space 6 | indent_size=2 7 | 8 | [{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}] 13 | indent_style=space 14 | indent_size=2 15 | 16 | [{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] 17 | indent_style=space 18 | indent_size=2 19 | 20 | [*.svg] 21 | indent_style=space 22 | indent_size=2 23 | 24 | [*.js.map] 25 | indent_style=space 26 | indent_size=2 27 | 28 | [*.less] 29 | indent_style=space 30 | indent_size=2 31 | 32 | [*.vue] 33 | indent_style=space 34 | indent_size=2 35 | 36 | [{.analysis_options,*.yml,*.yaml}] 37 | indent_style=space 38 | indent_size=2 39 | 40 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=false 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_PREVIEW=true 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /.env.preview: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=true 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/strongly-recommended', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'generator-star-spacing': 'off', 14 | 'no-mixed-operators': 0, 15 | 'vue/max-attributes-per-line': [ 16 | 2, 17 | { 18 | 'singleline': 5, 19 | 'multiline': { 20 | 'max': 1, 21 | 'allowFirstLine': false 22 | } 23 | } 24 | ], 25 | 'vue/attribute-hyphenation': 0, 26 | 'vue/html-self-closing': 0, 27 | 'vue/component-name-in-template-casing': 0, 28 | 'vue/html-closing-bracket-spacing': 0, 29 | 'vue/singleline-html-element-content-newline': 0, 30 | 'vue/no-unused-components': 0, 31 | 'vue/multiline-html-element-content-newline': 0, 32 | 'vue/no-use-v-if-with-v-for': 0, 33 | 'vue/html-closing-bracket-newline': 0, 34 | 'vue/no-parsing-error': 0, 35 | 'no-tabs': 0, 36 | 'quotes': [ 37 | 2, 38 | 'single', 39 | { 40 | 'avoidEscape': true, 41 | 'allowTemplateLiterals': true 42 | } 43 | ], 44 | 'semi': [ 45 | 2, 46 | 'never', 47 | { 48 | 'beforeStatementContinuationChars': 'never' 49 | } 50 | ], 51 | 'no-delete-var': 2, 52 | 'prefer-const': [ 53 | 2, 54 | { 55 | 'ignoreReadBeforeAssign': false 56 | } 57 | ], 58 | 'template-curly-spacing': 'off', 59 | 'indent': 'off' 60 | }, 61 | parserOptions: { 62 | parser: 'babel-eslint' 63 | }, 64 | overrides: [ 65 | { 66 | files: [ 67 | '**/__tests__/*.{j,t}s?(x)', 68 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 69 | ], 70 | env: { 71 | jest: true 72 | } 73 | } 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "space-before-function-paren": 0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | public/* linguist-vendored 2 | 3 | # Automatically normalize line endings (to LF) for all text-based files. 4 | * text=auto eol=lf 5 | 6 | # Declare files that will always have CRLF line endings on checkout. 7 | *.{cmd,[cC][mM][dD]} text eol=crlf 8 | *.{bat,[bB][aA][tT]} text eol=crlf 9 | 10 | # Denote all files that are truly binary and should not be modified. 11 | *.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve(Bug 反馈) 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug (描述 Bug)** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | 15 | 16 | **To Reproduce (重现步骤)** 17 | Steps to reproduce the behavior: 18 | 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | 25 | 26 | **Expected behavior(你期待的是什么?)** 27 | A clear and concise description of what you expected to happen. 28 | 29 | 30 | 31 | **Screenshots(截图)** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | 35 | 36 | **Desktop (please complete the following information):** 37 | 38 | - OS: [e.g. iOS] 39 | - Browser [e.g. chrome, safari] 40 | - Version [e.g. 22] 41 | 42 | 43 | 44 | **Smartphone (please complete the following information):** 45 | 46 | - Device: [e.g. iPhone6] 47 | - OS: [e.g. iOS8.1] 48 | - Browser [e.g. stock browser, safari] 49 | - Version [e.g. 22] 50 | 51 | 52 | 53 | **Additional context(附加信息)** 54 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/need-help-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Need help issue 3 | about: Question for use(问题求助) 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Question (问题描述)** 11 | How to use component `s-table` paging 12 | 13 | **Describe the solution you'd like (你期待的是什么?)** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context(附加信息)** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | First of all, thank you for your contribution! 😄 2 | 3 | Pull request will be merged after one of collaborators approve. 4 | Please makes sure that these form are filled before submitting your pull request, thank you! 5 | 6 | 7 | ### 这个变动的性质是 8 | 9 | - [ ] 新特性提交 10 | - [ ] 日常 bug 修复 11 | - [ ] 文档改进 12 | - [ ] 组件样式改进 13 | - [ ] 重构 14 | - [ ] 代码风格优化 15 | - [ ] 分支合并 16 | - [ ] 其他改动(是关于什么的改动?) 17 | 18 | ### 需求背景 19 | 20 | > 1. 描述相关需求的来源。 21 | > 2. 要解决的问题。 22 | > 3. 相关的 issue 讨论链接。 23 | 24 | ### 实现方案和 API(非新功能可选) 25 | 26 | > 1. 基本的解决思路和其他可选方案。 27 | > 2. 列出最终的 API 实现和用法。 28 | > 3. 涉及UI/交互变动需要有截图或 GIF。 29 | 30 | ### 对用户的影响和可能的风险(非新功能可选) 31 | 32 | > 1. 这个改动对用户端是否有影响?影响的方面有哪些? 33 | > 2. 是否有可能隐含的 break change 和其他风险? 34 | 35 | ### Changelog 描述(非新功能可选) 36 | 37 | > 1. 英文描述 38 | > 2. 中文描述(可选) 39 | 40 | ### 请求合并前的自查清单 41 | 42 | - [ ] 文档已补充或无须补充 43 | - [ ] 代码演示已提供或无须提供 44 | - [ ] Changelog 已提供或无须提供 45 | 46 | ### 后续计划(非新功能可选) 47 | 48 | > 如果这个提交后面还有相关的其他提交和跟进信息,可以写在这里。 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.js": "eslint --fix", 3 | "*.{css,less}": "stylelint --fix" 4 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": false, 4 | "singleQuote": true, 5 | "prettier.spaceBeforeFunctionParen": true 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.15.0 4 | cache: yarn 5 | script: 6 | - yarn 7 | - yarn run lint --no-fix && yarn run build 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | RUN rm /etc/nginx/conf.d/default.conf 4 | 5 | ADD deploy/nginx.conf /etc/nginx/conf.d/default.conf 6 | COPY dist/ /usr/share/nginx/html/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anan Yang 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. -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) 2 | const IS_PREVIEW = process.env.VUE_APP_PREVIEW === 'true' 3 | 4 | const plugins = [] 5 | if (IS_PROD && !IS_PREVIEW) { 6 | // 去除日志的插件, 7 | plugins.push('transform-remove-console') 8 | } 9 | 10 | // lazy load ant-design-vue 11 | // if your use import on Demand, Use this code 12 | plugins.push(['import', { 13 | 'libraryName': 'ant-design-vue', 14 | 'libraryDirectory': 'es', 15 | 'style': true // `style: true` 会加载 less 文件 16 | }]) 17 | 18 | module.exports = { 19 | presets: [ 20 | '@vue/cli-plugin-babel/preset', 21 | [ 22 | '@babel/preset-env', 23 | { 24 | 'useBuiltIns': 'entry', 25 | 'corejs': 3 26 | } 27 | ] 28 | ], 29 | plugins 30 | } 31 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * feat:新增功能 3 | * fix:bug 修复 4 | * docs:文档更新 5 | * style:不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑) 6 | * refactor:重构代码(既没有新增功能,也没有修复 bug) 7 | * perf:性能, 体验优化 8 | * test:新增测试用例或是更新现有测试 9 | * build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交 10 | * ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交 11 | * chore:不属于以上类型的其他类型,比如构建流程, 依赖管理 12 | * revert:回滚某个更早之前的提交 13 | */ 14 | 15 | module.exports = { 16 | extends: ['@commitlint/config-conventional'], 17 | rules: { 18 | 'type-enum': [ 19 | 2, 20 | 'always', 21 | ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert'] 22 | ], 23 | 'subject-full-stop': [0, 'never'], 24 | 'subject-case': [0, 'never'] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/themePluginConfig.js: -------------------------------------------------------------------------------- 1 | export default { 2 | theme: [ 3 | { 4 | key: 'dark', 5 | fileName: 'dark.css', 6 | theme: 'dark' 7 | }, 8 | { 9 | key: '#F5222D', 10 | fileName: '#F5222D.css', 11 | modifyVars: { 12 | '@primary-color': '#F5222D' 13 | } 14 | }, 15 | { 16 | key: '#FA541C', 17 | fileName: '#FA541C.css', 18 | modifyVars: { 19 | '@primary-color': '#FA541C' 20 | } 21 | }, 22 | { 23 | key: '#FAAD14', 24 | fileName: '#FAAD14.css', 25 | modifyVars: { 26 | '@primary-color': '#FAAD14' 27 | } 28 | }, 29 | { 30 | key: '#13C2C2', 31 | fileName: '#13C2C2.css', 32 | modifyVars: { 33 | '@primary-color': '#13C2C2' 34 | } 35 | }, 36 | { 37 | key: '#52C41A', 38 | fileName: '#52C41A.css', 39 | modifyVars: { 40 | '@primary-color': '#52C41A' 41 | } 42 | }, 43 | { 44 | key: '#2F54EB', 45 | fileName: '#2F54EB.css', 46 | modifyVars: { 47 | '@primary-color': '#2F54EB' 48 | } 49 | }, 50 | { 51 | key: '#722ED1', 52 | fileName: '#722ED1.css', 53 | modifyVars: { 54 | '@primary-color': '#722ED1' 55 | } 56 | }, 57 | 58 | { 59 | key: '#F5222D', 60 | theme: 'dark', 61 | fileName: 'dark-#F5222D.css', 62 | modifyVars: { 63 | '@primary-color': '#F5222D' 64 | } 65 | }, 66 | { 67 | key: '#FA541C', 68 | theme: 'dark', 69 | fileName: 'dark-#FA541C.css', 70 | modifyVars: { 71 | '@primary-color': '#FA541C' 72 | } 73 | }, 74 | { 75 | key: '#FAAD14', 76 | theme: 'dark', 77 | fileName: 'dark-#FAAD14.css', 78 | modifyVars: { 79 | '@primary-color': '#FAAD14' 80 | } 81 | }, 82 | { 83 | key: '#13C2C2', 84 | theme: 'dark', 85 | fileName: 'dark-#13C2C2.css', 86 | modifyVars: { 87 | '@primary-color': '#13C2C2' 88 | } 89 | }, 90 | { 91 | key: '#52C41A', 92 | theme: 'dark', 93 | fileName: 'dark-#52C41A.css', 94 | modifyVars: { 95 | '@primary-color': '#52C41A' 96 | } 97 | }, 98 | { 99 | key: '#2F54EB', 100 | theme: 'dark', 101 | fileName: 'dark-#2F54EB.css', 102 | modifyVars: { 103 | '@primary-color': '#2F54EB' 104 | } 105 | }, 106 | { 107 | key: '#722ED1', 108 | theme: 'dark', 109 | fileName: 'dark-#722ED1.css', 110 | modifyVars: { 111 | '@primary-color': '#722ED1' 112 | } 113 | } 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /deploy/caddy.conf: -------------------------------------------------------------------------------- 1 | 0.0.0.0:80 { 2 | gzip 3 | root /usr/share/nginx/html 4 | 5 | rewrite { 6 | r .* 7 | to {path} / 8 | } 9 | } -------------------------------------------------------------------------------- /deploy/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name _; 4 | # gzip config 5 | gzip on; 6 | gzip_min_length 1k; 7 | gzip_comp_level 6; 8 | gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; 9 | gzip_vary on; 10 | gzip_disable "MSIE [1-6]\."; 11 | 12 | root /usr/share/nginx/html; 13 | include /etc/nginx/mime.types; 14 | 15 | location / { 16 | try_files $uri $uri/ /index.html; 17 | } 18 | 19 | # location /api { 20 | # proxy_pass https://preview.pro.antdv.com/api; 21 | # proxy_set_header X-Forwarded-Proto $scheme; 22 | # proxy_set_header X-Real-IP $remote_addr; 23 | # } 24 | } 25 | -------------------------------------------------------------------------------- /docs/add-page-loading-animate.md: -------------------------------------------------------------------------------- 1 | 为首屏增加 加载动画 2 | ==== 3 | 4 | 5 | 6 | ## 需求 7 | 8 | > 为了缓解用户第一次访问时,加载 JS 过大所导致用户等待白屏时间过长导致的用户体验不好,进行的一个优化动效。 9 | 10 | 11 | 12 | ## 实现方案 13 | 14 | 1. 将 动画加载 dom 元素放在 #app 内,Vue 生命周期开始时,会自动清掉 #app 下的所有元素。 15 | 2. 将 动画加载 dom 元素放在 body 下,Vue 生命周期开始时 App.vue (created, mounted) 调用 `@/utils/utll` 下的 removeLoadingAnimate(#id, timeout) 则会移除加载动画 16 | 17 | 最后一步: 18 | ​ 将样式插入到 `public/index.html` 文件的 `` 最好写成内联 `` 19 | 20 | 21 | 22 | ---- 23 | 24 | 目前提供有两个样式,均在 `public/loading` 文件夹内。且 pro 已经默认使用了一套 loading 动画方案,可以直接参考 `public/index.html` 25 | 26 | 27 | ## 写在最后 28 | 29 | 目前 pro 有页面 overflow 显示出浏览器滚动条时,页面会抖动一下的问题。 30 | 31 | 欢迎各位提供能解决的方案和实现 demo。如果在条件允许的情况下,建议请直接使用 pro 进行改造,也欢迎直接 PR 到 pro 的仓库 32 | -------------------------------------------------------------------------------- /docs/webpack-bundle-analyzer.md: -------------------------------------------------------------------------------- 1 | 先增加依赖 2 | 3 | ```bash 4 | // npm 5 | $ npm install --save-dev webpack-bundle-analyzer 6 | 7 | // or yarn 8 | $ yarn add webpack-bundle-analyzer -D 9 | ``` 10 | 11 | 配置文件 `vue.config.js` 增加 `configureWebpack.plugins` 参数 12 | 13 | ``` 14 | const path = require('path') 15 | const webpack = require('webpack') 16 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 17 | 18 | function resolve (dir) { 19 | return path.join(__dirname, dir) 20 | } 21 | 22 | // vue.config.js 23 | module.exports = { 24 | configureWebpack: { 25 | plugins: [ 26 | // Ignore all locale files of moment.js 27 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 28 | // 依赖大小分析工具 29 | new BundleAnalyzerPlugin(), 30 | ] 31 | }, 32 | 33 | 34 | ... 35 | } 36 | ``` 37 | 38 | 39 | 40 | 启动 `cli` 的 `build` 命令进行项目编译,编译完成时,会自动运行一个 http://localhost:8888 的地址,完整显示了支持库依赖 -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/' 23 | } 24 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "dist"], 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vueComponent/ant-design-vue-pro/08a7aad7229311320de201ebedc0e1bd6df526a8/public/avatar2.jpg -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ant Design Pro 9 | 10 | 11 | <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> 12 | 13 | <% } %> 14 | 15 | 16 | 19 |
20 |
21 |

Pro

22 |
23 | 24 |
25 |
Ant Design
26 |
27 |
28 | 29 | <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> 30 | 31 | <% } %> 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vueComponent/ant-design-vue-pro/08a7aad7229311320de201ebedc0e1bd6df526a8/public/logo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const userApi = { 4 | Login: '/auth/login', 5 | Logout: '/auth/logout', 6 | ForgePassword: '/auth/forge-password', 7 | Register: '/auth/register', 8 | twoStepCode: '/auth/2step-code', 9 | SendSms: '/account/sms', 10 | SendSmsErr: '/account/sms_err', 11 | // get my info 12 | UserInfo: '/user/info', 13 | UserMenu: '/user/nav' 14 | } 15 | 16 | /** 17 | * login func 18 | * parameter: { 19 | * username: '', 20 | * password: '', 21 | * remember_me: true, 22 | * captcha: '12345' 23 | * } 24 | * @param parameter 25 | * @returns {*} 26 | */ 27 | export function login (parameter) { 28 | return request({ 29 | url: userApi.Login, 30 | method: 'post', 31 | data: parameter 32 | }) 33 | } 34 | 35 | export function getSmsCaptcha (parameter) { 36 | return request({ 37 | url: userApi.SendSms, 38 | method: 'post', 39 | data: parameter 40 | }) 41 | } 42 | 43 | export function getInfo () { 44 | return request({ 45 | url: userApi.UserInfo, 46 | method: 'get', 47 | headers: { 48 | 'Content-Type': 'application/json;charset=UTF-8' 49 | } 50 | }) 51 | } 52 | 53 | export function getCurrentUserNav () { 54 | return request({ 55 | url: userApi.UserMenu, 56 | method: 'get' 57 | }) 58 | } 59 | 60 | export function logout () { 61 | return request({ 62 | url: userApi.Logout, 63 | method: 'post', 64 | headers: { 65 | 'Content-Type': 'application/json;charset=UTF-8' 66 | } 67 | }) 68 | } 69 | 70 | /** 71 | * get user 2step code open? 72 | * @param parameter {*} 73 | */ 74 | export function get2step (parameter) { 75 | return request({ 76 | url: userApi.twoStepCode, 77 | method: 'post', 78 | data: parameter 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /src/api/manage.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const api = { 4 | user: '/user', 5 | role: '/role', 6 | service: '/service', 7 | permission: '/permission', 8 | permissionNoPager: '/permission/no-pager', 9 | orgTree: '/org/tree' 10 | } 11 | 12 | export default api 13 | 14 | export function getUserList (parameter) { 15 | return request({ 16 | url: api.user, 17 | method: 'get', 18 | params: parameter 19 | }) 20 | } 21 | 22 | export function getRoleList (parameter) { 23 | return request({ 24 | url: api.role, 25 | method: 'get', 26 | params: parameter 27 | }) 28 | } 29 | 30 | export function getServiceList (parameter) { 31 | return request({ 32 | url: api.service, 33 | method: 'get', 34 | params: parameter 35 | }) 36 | } 37 | 38 | export function getPermissions (parameter) { 39 | return request({ 40 | url: api.permissionNoPager, 41 | method: 'get', 42 | params: parameter 43 | }) 44 | } 45 | 46 | export function getOrgTree (parameter) { 47 | return request({ 48 | url: api.orgTree, 49 | method: 'get', 50 | params: parameter 51 | }) 52 | } 53 | 54 | // id == 0 add post 55 | // id != 0 update put 56 | export function saveService (parameter) { 57 | return request({ 58 | url: api.service, 59 | method: parameter.id === 0 ? 'post' : 'put', 60 | data: parameter 61 | }) 62 | } 63 | 64 | export function saveSub (sub) { 65 | return request({ 66 | url: '/sub', 67 | method: sub.id === 0 ? 'post' : 'put', 68 | data: sub 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/assets/icons/bx-analyse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ArticleListContent/ArticleListContent.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 47 | 48 | 90 | -------------------------------------------------------------------------------- /src/components/ArticleListContent/index.js: -------------------------------------------------------------------------------- 1 | import ArticleListContent from './ArticleListContent' 2 | 3 | export default ArticleListContent 4 | -------------------------------------------------------------------------------- /src/components/AvatarList/Item.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'ant-design-vue/es/_util/vue-types' 2 | import { Tooltip, Avatar } from 'ant-design-vue' 3 | import { getSlotOptions } from 'ant-design-vue/lib/_util/props-util' 4 | import { warning } from 'ant-design-vue/lib/vc-util/warning' 5 | 6 | export const AvatarListItemProps = { 7 | tips: PropTypes.string, 8 | src: PropTypes.string.def('') 9 | } 10 | 11 | const Item = { 12 | __ANT_AVATAR_CHILDREN: true, 13 | name: 'AvatarListItem', 14 | props: AvatarListItemProps, 15 | created () { 16 | warning(getSlotOptions(this.$parent).__ANT_AVATAR_LIST, 'AvatarListItem must be a subcomponent of AvatarList') 17 | }, 18 | render () { 19 | const size = this.$parent.size === 'mini' ? 'small' : this.$parent.size 20 | const AvatarDom = 21 | return (this.tips && {AvatarDom}) || 22 | } 23 | } 24 | 25 | export default Item 26 | -------------------------------------------------------------------------------- /src/components/AvatarList/List.jsx: -------------------------------------------------------------------------------- 1 | import './index.less' 2 | 3 | import PropTypes from 'ant-design-vue/es/_util/vue-types' 4 | import Avatar from 'ant-design-vue/es/avatar' 5 | import Item from './Item.jsx' 6 | import { filterEmpty } from '@/components/_util/util' 7 | 8 | /** 9 | * size: `number`、 `large`、`small`、`default` 默认值: default 10 | * maxLength: number 11 | * excessItemsStyle: CSSProperties 12 | */ 13 | const AvatarListProps = { 14 | prefixCls: PropTypes.string.def('ant-pro-avatar-list'), 15 | size: { 16 | validator: val => { 17 | return typeof val === 'number' || ['small', 'large', 'default'].includes(val) 18 | }, 19 | default: 'default' 20 | }, 21 | maxLength: PropTypes.number.def(0), 22 | excessItemsStyle: PropTypes.object.def({ 23 | color: '#f56a00', 24 | backgroundColor: '#fde3cf' 25 | }) 26 | } 27 | 28 | const AvatarList = { 29 | __ANT_AVATAR_LIST: true, 30 | Item, 31 | name: 'AvatarList', 32 | props: AvatarListProps, 33 | render (h) { 34 | const { prefixCls, size } = this.$props 35 | const className = { 36 | [`${prefixCls}`]: true, 37 | [`${size}`]: true 38 | } 39 | 40 | const items = filterEmpty(this.$slots.default) 41 | const itemsDom = items && items.length ?
    {this.getItems(items)}
: null 42 | return ( 43 |
44 | {itemsDom} 45 |
46 | ) 47 | }, 48 | methods: { 49 | getItems (items) { 50 | const className = { 51 | [`${this.prefixCls}-item`]: true, 52 | [`${this.size}`]: true 53 | } 54 | const totalSize = items.length 55 | 56 | if (this.maxLength > 0) { 57 | items = items.slice(0, this.maxLength) 58 | items.push(({`+${totalSize - this.maxLength}`})) 59 | } 60 | return items.map((item) => ( 61 |
  • {item}
  • 62 | )) 63 | } 64 | } 65 | } 66 | 67 | AvatarList.install = function (Vue) { 68 | Vue.component(AvatarList.name, AvatarList) 69 | Vue.component(AvatarList.Item.name, AvatarList.Item) 70 | } 71 | 72 | export default AvatarList 73 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.js: -------------------------------------------------------------------------------- 1 | import AvatarList from './List' 2 | import Item from './Item' 3 | 4 | export { 5 | AvatarList, 6 | Item as AvatarListItem 7 | } 8 | 9 | export default AvatarList 10 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list"; 4 | @avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item"; 5 | 6 | .@{avatar-list-prefix-cls} { 7 | display: inline-block; 8 | 9 | ul { 10 | display: inline-block; 11 | padding: 0; 12 | margin: 0 0 0 8px; 13 | font-size: 0; 14 | list-style: none; 15 | } 16 | } 17 | 18 | .@{avatar-list-item-prefix-cls} { 19 | display: inline-block; 20 | width: @avatar-size-base; 21 | height: @avatar-size-base; 22 | margin-left: -8px; 23 | font-size: @font-size-base; 24 | 25 | :global { 26 | .ant-avatar { 27 | cursor: pointer; 28 | border: 1px solid #fff; 29 | } 30 | } 31 | 32 | &.large { 33 | width: @avatar-size-lg; 34 | height: @avatar-size-lg; 35 | } 36 | 37 | &.small { 38 | width: @avatar-size-sm; 39 | height: @avatar-size-sm; 40 | } 41 | 42 | &.mini { 43 | width: 20px; 44 | height: 20px; 45 | 46 | :global { 47 | .ant-avatar { 48 | width: 20px; 49 | height: 20px; 50 | line-height: 20px; 51 | 52 | .ant-avatar-string { 53 | font-size: 12px; 54 | line-height: 18px; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/AvatarList/index.md: -------------------------------------------------------------------------------- 1 | # AvatarList 用户头像列表 2 | 3 | 4 | 一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。 5 | 6 | 7 | 8 | 引用方式: 9 | 10 | ```javascript 11 | import AvatarList from '@/components/AvatarList' 12 | const AvatarListItem = AvatarList.Item 13 | 14 | export default { 15 | components: { 16 | AvatarList, 17 | AvatarListItem 18 | } 19 | } 20 | ``` 21 | 22 | 23 | 24 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 25 | 26 | ```html 27 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 或 34 | ```html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | 46 | 47 | 48 | ## API 49 | 50 | ### AvatarList 51 | 52 | | 参数 | 说明 | 类型 | 默认值 | 53 | | ---------------- | -------- | ---------------------------------- | --------- | 54 | | size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` | 55 | | maxLength | 要显示的最大项目 | number | - | 56 | | excessItemsStyle | 多余的项目风格 | CSSProperties | - | 57 | 58 | ### AvatarList.Item 59 | 60 | | 参数 | 说明 | 类型 | 默认值 | 61 | | ---- | ------ | --------- | --- | 62 | | tips | 头像展示文案 | string | - | 63 | | src | 头像图片连接 | string | - | 64 | 65 | -------------------------------------------------------------------------------- /src/components/Charts/Bar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 63 | -------------------------------------------------------------------------------- /src/components/Charts/ChartCard.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 53 | 54 | 121 | -------------------------------------------------------------------------------- /src/components/Charts/Liquid.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /src/components/Charts/MiniArea.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 53 | 54 | 57 | -------------------------------------------------------------------------------- /src/components/Charts/MiniBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 54 | 55 | 58 | -------------------------------------------------------------------------------- /src/components/Charts/MiniProgress.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | 37 | 76 | -------------------------------------------------------------------------------- /src/components/Charts/MiniSmoothArea.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /src/components/Charts/Radar.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /src/components/Charts/RankList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | 31 | 78 | -------------------------------------------------------------------------------- /src/components/Charts/TransferBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 65 | -------------------------------------------------------------------------------- /src/components/Charts/Trend.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 52 | 53 | 83 | -------------------------------------------------------------------------------- /src/components/Charts/chart.less: -------------------------------------------------------------------------------- 1 | .antv-chart-mini { 2 | position: relative; 3 | width: 100%; 4 | 5 | .chart-wrapper { 6 | position: absolute; 7 | bottom: -28px; 8 | width: 100%; 9 | 10 | /* margin: 0 -5px; 11 | overflow: hidden; */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Charts/smooth.area.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @smoothArea-prefix-cls: ~"@{ant-pro-prefix}-smooth-area"; 4 | 5 | .@{smoothArea-prefix-cls} { 6 | position: relative; 7 | width: 100%; 8 | 9 | .chart-wrapper { 10 | position: absolute; 11 | bottom: -28px; 12 | width: 100%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Editor/QuillEditor.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 69 | 70 | 84 | -------------------------------------------------------------------------------- /src/components/Editor/WangEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /src/components/Ellipsis/Ellipsis.vue: -------------------------------------------------------------------------------- 1 | 65 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.js: -------------------------------------------------------------------------------- 1 | import Ellipsis from './Ellipsis' 2 | 3 | export default Ellipsis 4 | -------------------------------------------------------------------------------- /src/components/Ellipsis/index.md: -------------------------------------------------------------------------------- 1 | # Ellipsis 文本自动省略号 2 | 3 | 文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import Ellipsis from '@/components/Ellipsis' 11 | 12 | export default { 13 | components: { 14 | Ellipsis 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 25 | There were injuries alleged in three cases in 2015, and a 26 | fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall. 27 | 28 | ``` 29 | 30 | 31 | 32 | ## API 33 | 34 | 35 | 参数 | 说明 | 类型 | 默认值 36 | ----|------|-----|------ 37 | tooltip | 移动到文本展示完整内容的提示 | boolean | - 38 | length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - -------------------------------------------------------------------------------- /src/components/FooterToolbar/FooterToolBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.js: -------------------------------------------------------------------------------- 1 | import FooterToolBar from './FooterToolBar' 2 | import './index.less' 3 | 4 | export default FooterToolBar 5 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar"; 4 | 5 | .@{footer-toolbar-prefix-cls} { 6 | position: fixed; 7 | right: 0; 8 | bottom: 0; 9 | z-index: 9; 10 | width: 100%; 11 | height: 56px; 12 | padding: 0 24px; 13 | line-height: 56px; 14 | background: #fff; 15 | box-shadow: 0 -1px 2px rgb(0 0 0 / 3%); 16 | border-top: 1px solid #e8e8e8; 17 | 18 | &::after { 19 | display: block; 20 | clear: both; 21 | content: ''; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/FooterToolbar/index.md: -------------------------------------------------------------------------------- 1 | # FooterToolbar 底部工具栏 2 | 3 | 固定在底部的工具栏。 4 | 5 | 6 | 7 | ## 何时使用 8 | 9 | 固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 10 | 11 | 12 | 13 | 引用方式: 14 | 15 | ```javascript 16 | import FooterToolBar from '@/components/FooterToolbar' 17 | 18 | export default { 19 | components: { 20 | FooterToolBar 21 | } 22 | } 23 | ``` 24 | 25 | 26 | 27 | ## 代码演示 28 | 29 | ```html 30 | 31 | 提交 32 | 33 | ``` 34 | 或 35 | ```html 36 | 37 | 提交 38 | 39 | ``` 40 | 41 | 42 | ## API 43 | 44 | 参数 | 说明 | 类型 | 默认值 45 | ----|------|-----|------ 46 | children (slot) | 工具栏内容,向右对齐 | - | - 47 | extra | 额外信息,向左对齐 | String, Object | - 48 | 49 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /src/components/GlobalHeader/AvatarDropdown.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 70 | 71 | 81 | -------------------------------------------------------------------------------- /src/components/GlobalHeader/RightContent.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 59 | -------------------------------------------------------------------------------- /src/components/IconSelector/IconSelector.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 62 | 63 | 87 | -------------------------------------------------------------------------------- /src/components/IconSelector/README.md: -------------------------------------------------------------------------------- 1 | IconSelector 2 | ==== 3 | 4 | > 图标选择组件,常用于为某一个数据设定一个图标时使用 5 | > eg: 设定菜单列表时,为每个菜单设定一个图标 6 | 7 | 该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装 8 | 9 | 10 | 11 | ### 使用方式 12 | 13 | ```vue 14 | 19 | 20 | 39 | ``` 40 | 41 | 42 | 43 | ### 事件 44 | 45 | 46 | | 名称 | 说明 | 类型 | 默认值 | 47 | | ------ | -------------------------- | ------ | ------ | 48 | | change | 当改变了 `icon` 选中项触发 | String | - | 49 | -------------------------------------------------------------------------------- /src/components/IconSelector/index.js: -------------------------------------------------------------------------------- 1 | import IconSelector from './IconSelector' 2 | export default IconSelector 3 | -------------------------------------------------------------------------------- /src/components/MultiTab/events.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | export default new Vue() 3 | -------------------------------------------------------------------------------- /src/components/MultiTab/index.js: -------------------------------------------------------------------------------- 1 | import events from './events' 2 | import MultiTab from './MultiTab' 3 | import './index.less' 4 | 5 | const api = { 6 | /** 7 | * open new tab on route fullPath 8 | * @param config 9 | */ 10 | open: function (config) { 11 | events.$emit('open', config) 12 | }, 13 | rename: function (key, name) { 14 | events.$emit('rename', { key: key, name: name }) 15 | }, 16 | /** 17 | * close current page 18 | */ 19 | closeCurrentPage: function () { 20 | this.close() 21 | }, 22 | /** 23 | * close route fullPath tab 24 | * @param config 25 | */ 26 | close: function (config) { 27 | events.$emit('close', config) 28 | } 29 | } 30 | 31 | MultiTab.install = function (Vue) { 32 | if (Vue.prototype.$multiTab) { 33 | return 34 | } 35 | api.instance = events 36 | Vue.prototype.$multiTab = api 37 | Vue.component('multi-tab', MultiTab) 38 | } 39 | 40 | export default MultiTab 41 | -------------------------------------------------------------------------------- /src/components/MultiTab/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab"; 4 | @multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper"; 5 | 6 | /* 7 | .topmenu .@{multi-tab-prefix-cls} { 8 | max-width: 1200px; 9 | margin: -23px auto 24px auto; 10 | } 11 | */ 12 | .@{multi-tab-prefix-cls} { 13 | margin: -23px -24px 24px; 14 | background: #fff; 15 | } 16 | 17 | .topmenu .@{multi-tab-wrapper-prefix-cls} { 18 | max-width: 1200px; 19 | margin: 0 auto; 20 | } 21 | 22 | .topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} { 23 | max-width: 100%; 24 | margin: 0 auto; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/NProgress/nprogress.less: -------------------------------------------------------------------------------- 1 | @import url('../index.less'); 2 | 3 | /* Make clicks pass-through */ 4 | #nprogress { 5 | pointer-events: none; 6 | } 7 | 8 | #nprogress .bar { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | z-index: 1031; 13 | width: 100%; 14 | height: 2px; 15 | background: @primary-color; 16 | } 17 | 18 | /* Fancy blur effect */ 19 | #nprogress .peg { 20 | position: absolute; 21 | right: 0; 22 | display: block; 23 | width: 100px; 24 | height: 100%; 25 | opacity: 1; 26 | transform: rotate(3deg) translate(0, -4px); 27 | transform: rotate(3deg) translate(0, -4px); 28 | transform: rotate(3deg) translate(0, -4px); 29 | box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color; 30 | } 31 | 32 | /* Remove these to get rid of the spinner */ 33 | #nprogress .spinner { 34 | position: fixed; 35 | top: 15px; 36 | right: 15px; 37 | z-index: 1031; 38 | display: block; 39 | } 40 | 41 | #nprogress .spinner-icon { 42 | width: 18px; 43 | height: 18px; 44 | box-sizing: border-box; 45 | border: solid 2px transparent; 46 | border-top-color: @primary-color; 47 | border-left-color: @primary-color; 48 | border-radius: 50%; 49 | animation: nprogress-spinner 400ms linear infinite; 50 | animation: nprogress-spinner 400ms linear infinite; 51 | } 52 | 53 | .nprogress-custom-parent { 54 | position: relative; 55 | overflow: hidden; 56 | } 57 | 58 | .nprogress-custom-parent #nprogress .spinner, 59 | .nprogress-custom-parent #nprogress .bar { 60 | position: absolute; 61 | } 62 | 63 | @keyframes nprogress-spinner { 64 | 0% { transform: rotate(0deg); } 65 | 100% { transform: rotate(360deg); } 66 | } 67 | @keyframes nprogress-spinner { 68 | 0% { transform: rotate(0deg); } 69 | 100% { transform: rotate(360deg); } 70 | } 71 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/NoticeIcon.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 75 | 76 | 81 | 91 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/index.js: -------------------------------------------------------------------------------- 1 | import NoticeIcon from './NoticeIcon' 2 | export default NoticeIcon 3 | -------------------------------------------------------------------------------- /src/components/NumberInfo/NumberInfo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.js: -------------------------------------------------------------------------------- 1 | import NumberInfo from './NumberInfo' 2 | 3 | export default NumberInfo 4 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info"; 4 | 5 | .@{numberInfo-prefix-cls} { 6 | .ant-pro-number-info-subtitle { 7 | height: 22px; 8 | overflow: hidden; 9 | font-size: @font-size-base; 10 | line-height: 22px; 11 | color: @text-color-secondary; 12 | text-overflow: ellipsis; 13 | word-break: break-all; 14 | white-space: nowrap; 15 | } 16 | 17 | .number-info-value { 18 | margin-top: 4px; 19 | overflow: hidden; 20 | font-size: 0; 21 | text-overflow: ellipsis; 22 | word-break: break-all; 23 | white-space: nowrap; 24 | 25 | & > span { 26 | display: inline-block; 27 | height: 32px; 28 | margin-right: 32px; 29 | font-size: 24px; 30 | line-height: 32px; 31 | color: @heading-color; 32 | } 33 | 34 | .sub-total { 35 | margin-right: 0; 36 | font-size: @font-size-lg; 37 | color: @text-color-secondary; 38 | vertical-align: top; 39 | 40 | i { 41 | margin-left: 4px; 42 | font-size: 12px; 43 | transform: scale(.82); 44 | } 45 | 46 | .anticon-caret-up { 47 | color: @red-6; 48 | } 49 | 50 | .anticon-caret-down { 51 | color: @green-6; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.md: -------------------------------------------------------------------------------- 1 | # NumberInfo 数据文本 2 | 3 | 常用在数据卡片中,用于突出展示某个业务数据。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import NumberInfo from '@/components/NumberInfo' 11 | 12 | export default { 13 | components: { 14 | NumberInfo 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 29 | ``` 30 | 31 | 32 | 33 | ## API 34 | 35 | 参数 | 说明 | 类型 | 默认值 36 | ----|------|-----|------ 37 | title | 标题 | ReactNode\|string | - 38 | subTitle | 子标题 | ReactNode\|string | - 39 | total | 总量 | ReactNode\|string | - 40 | subTotal | 子总量 | ReactNode\|string | - 41 | status | 增加状态 | 'up \| down' | - 42 | theme | 状态样式 | string | 'light' 43 | gap | 设置数字和描述之间的间距(像素)| number | 8 44 | -------------------------------------------------------------------------------- /src/components/Other/CarbonAds.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 63 | -------------------------------------------------------------------------------- /src/components/PageLoading/index.jsx: -------------------------------------------------------------------------------- 1 | import { Spin } from 'ant-design-vue' 2 | 3 | export const PageLoading = { 4 | name: 'PageLoading', 5 | props: { 6 | tip: { 7 | type: String, 8 | default: 'Loading..' 9 | }, 10 | size: { 11 | type: String, 12 | default: 'large' 13 | } 14 | }, 15 | render () { 16 | const style = { 17 | textAlign: 'center', 18 | background: 'rgba(0,0,0,0.6)', 19 | position: 'fixed', 20 | top: 0, 21 | bottom: 0, 22 | left: 0, 23 | right: 0, 24 | zIndex: 1100 25 | } 26 | const spinStyle = { 27 | position: 'absolute', 28 | left: '50%', 29 | top: '40%', 30 | transform: 'translate(-50%, -50%)' 31 | } 32 | return (
    33 | 34 |
    ) 35 | } 36 | } 37 | 38 | const version = '0.0.1' 39 | const loading = {} 40 | 41 | loading.newInstance = (Vue, options) => { 42 | let loadingElement = document.querySelector('body>div[type=loading]') 43 | if (!loadingElement) { 44 | loadingElement = document.createElement('div') 45 | loadingElement.setAttribute('type', 'loading') 46 | loadingElement.setAttribute('class', 'ant-loading-wrapper') 47 | document.body.appendChild(loadingElement) 48 | } 49 | 50 | const cdProps = Object.assign({ visible: false, size: 'large', tip: 'Loading...' }, options) 51 | 52 | const instance = new Vue({ 53 | data () { 54 | return { 55 | ...cdProps 56 | } 57 | }, 58 | render () { 59 | const { tip } = this 60 | const props = {} 61 | this.tip && (props.tip = tip) 62 | if (this.visible) { 63 | return 64 | } 65 | return null 66 | } 67 | }).$mount(loadingElement) 68 | 69 | function update (config) { 70 | const { visible, size, tip } = { ...cdProps, ...config } 71 | instance.$set(instance, 'visible', visible) 72 | if (tip) { 73 | instance.$set(instance, 'tip', tip) 74 | } 75 | if (size) { 76 | instance.$set(instance, 'size', size) 77 | } 78 | } 79 | 80 | return { 81 | instance, 82 | update 83 | } 84 | } 85 | 86 | const api = { 87 | show: function (options) { 88 | this.instance.update({ ...options, visible: true }) 89 | }, 90 | hide: function () { 91 | this.instance.update({ visible: false }) 92 | } 93 | } 94 | 95 | const install = function (Vue, options) { 96 | if (Vue.prototype.$loading) { 97 | return 98 | } 99 | api.instance = loading.newInstance(Vue, options) 100 | Vue.prototype.$loading = api 101 | } 102 | 103 | export default { 104 | version, 105 | install 106 | } 107 | -------------------------------------------------------------------------------- /src/components/Search/GlobalSearch.jsx: -------------------------------------------------------------------------------- 1 | import { Select } from 'ant-design-vue' 2 | import './index.less' 3 | 4 | const GlobalSearch = { 5 | name: 'GlobalSearch', 6 | data () { 7 | return { 8 | visible: false 9 | } 10 | }, 11 | mounted () { 12 | const keyboardHandle = (e) => { 13 | e.preventDefault() 14 | e.stopPropagation() 15 | const { ctrlKey, shiftKey, altKey, keyCode } = e 16 | console.log('keyCode:', e.keyCode, e) 17 | // key is `K` and hold ctrl 18 | if (keyCode === 75 && ctrlKey && !shiftKey && !altKey) { 19 | this.visible = !this.visible 20 | } 21 | } 22 | document.addEventListener('keydown', keyboardHandle) 23 | }, 24 | render () { 25 | const { visible } = this 26 | const handleSearch = (e) => { 27 | this.$emit('search', e) 28 | } 29 | 30 | const handleChange = (e) => { 31 | this.$emit('change', e) 32 | } 33 | if (!visible) { 34 | return null 35 | } 36 | return ( 37 | 55 | ) 56 | } 57 | } 58 | 59 | GlobalSearch.install = function (Vue) { 60 | Vue.component(GlobalSearch.name, GlobalSearch) 61 | } 62 | 63 | export default GlobalSearch 64 | -------------------------------------------------------------------------------- /src/components/Search/index.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default'; 2 | 3 | .global-search-wrapper { 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | z-index: @zindex-modal-mask; 10 | background: @modal-mask-bg; 11 | 12 | .global-search-box { 13 | position: absolute; 14 | top: 20%; 15 | left: 50%; 16 | width: 450px; 17 | transform: translate(-50%, -50%); 18 | 19 | .global-search-tips { 20 | font-size: @font-size-lg; 21 | color: @white; 22 | text-align: right; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/SelectLang/index.jsx: -------------------------------------------------------------------------------- 1 | import './index.less' 2 | 3 | import { Icon, Menu, Dropdown } from 'ant-design-vue' 4 | import { i18nRender } from '@/locales' 5 | import i18nMixin from '@/store/i18n-mixin' 6 | 7 | const locales = ['zh-CN', 'en-US'] 8 | const languageLabels = { 9 | 'zh-CN': '简体中文', 10 | 'en-US': 'English' 11 | } 12 | // eslint-disable-next-line 13 | const languageIcons = { 14 | 'zh-CN': '🇨🇳', 15 | 'en-US': '🇺🇸' 16 | } 17 | 18 | const SelectLang = { 19 | props: { 20 | prefixCls: { 21 | type: String, 22 | default: 'ant-pro-drop-down' 23 | } 24 | }, 25 | name: 'SelectLang', 26 | mixins: [i18nMixin], 27 | render () { 28 | const { prefixCls } = this 29 | const changeLang = ({ key }) => { 30 | this.setLang(key) 31 | } 32 | const langMenu = ( 33 | 34 | {locales.map(locale => ( 35 | 36 | 37 | {languageIcons[locale]} 38 | {' '} 39 | {languageLabels[locale]} 40 | 41 | ))} 42 | 43 | ) 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | } 53 | 54 | export default SelectLang 55 | -------------------------------------------------------------------------------- /src/components/SelectLang/index.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default'; 2 | 3 | @header-menu-prefix-cls: ~'@{ant-prefix}-pro-header-menu'; 4 | @header-drop-down-prefix-cls: ~'@{ant-prefix}-pro-drop-down'; 5 | 6 | .@{header-menu-prefix-cls} { 7 | .anticon { 8 | margin-right: 8px; 9 | } 10 | 11 | .ant-dropdown-menu-item { 12 | min-width: 160px; 13 | } 14 | } 15 | 16 | .@{header-drop-down-prefix-cls} { 17 | line-height: @layout-header-height; 18 | vertical-align: top; 19 | cursor: pointer; 20 | 21 | > i { 22 | font-size: 16px !important; 23 | transform: none !important; 24 | 25 | svg { 26 | position: relative; 27 | top: -1px; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/SettingItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 39 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/index.js: -------------------------------------------------------------------------------- 1 | import SettingDrawer from './SettingDrawer' 2 | export default SettingDrawer 3 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/settingConfig.js: -------------------------------------------------------------------------------- 1 | import message from 'ant-design-vue/es/message' 2 | // import defaultSettings from '../defaultSettings'; 3 | import themeColor from './themeColor.js' 4 | 5 | // let lessNodesAppended 6 | const colorList = [ 7 | { 8 | key: '薄暮', color: '#F5222D' 9 | }, 10 | { 11 | key: '火山', color: '#FA541C' 12 | }, 13 | { 14 | key: '日暮', color: '#FAAD14' 15 | }, 16 | { 17 | key: '明青', color: '#13C2C2' 18 | }, 19 | { 20 | key: '极光绿', color: '#52C41A' 21 | }, 22 | { 23 | key: '拂晓蓝(默认)', color: '#1890FF' 24 | }, 25 | { 26 | key: '极客蓝', color: '#2F54EB' 27 | }, 28 | { 29 | key: '酱紫', color: '#722ED1' 30 | } 31 | ] 32 | 33 | const updateTheme = newPrimaryColor => { 34 | const hideMessage = message.loading('正在切换主题!', 0) 35 | themeColor.changeColor(newPrimaryColor).finally(() => { 36 | setTimeout(() => { 37 | hideMessage() 38 | }, 10) 39 | }) 40 | } 41 | 42 | const updateColorWeak = colorWeak => { 43 | // document.body.className = colorWeak ? 'colorWeak' : ''; 44 | const app = document.body.querySelector('#app') 45 | colorWeak ? app.classList.add('colorWeak') : app.classList.remove('colorWeak') 46 | } 47 | 48 | export { updateTheme, colorList, updateColorWeak } 49 | -------------------------------------------------------------------------------- /src/components/SettingDrawer/themeColor.js: -------------------------------------------------------------------------------- 1 | import client from 'webpack-theme-color-replacer/client' 2 | import generate from '@ant-design/colors/lib/generate' 3 | 4 | export default { 5 | getAntdSerials (color) { 6 | // 淡化(即less的tint) 7 | const lightens = new Array(9).fill().map((t, i) => { 8 | return client.varyColor.lighten(color, i / 10) 9 | }) 10 | // colorPalette变换得到颜色值 11 | const colorPalettes = generate(color) 12 | const rgb = client.varyColor.toNum3(color.replace('#', '')).join(',') 13 | return lightens.concat(colorPalettes).concat(rgb) 14 | }, 15 | changeColor (newColor) { 16 | var options = { 17 | newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors` 18 | changeUrl (cssUrl) { 19 | return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path 20 | } 21 | } 22 | return client.changer.changeColor(options, Promise) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/StandardFormRow/index.js: -------------------------------------------------------------------------------- 1 | import StandardFormRow from './StandardFormRow' 2 | 3 | export default StandardFormRow 4 | -------------------------------------------------------------------------------- /src/components/TagSelect/TagSelectOption.jsx: -------------------------------------------------------------------------------- 1 | import { Tag } from 'ant-design-vue' 2 | const { CheckableTag } = Tag 3 | 4 | export default { 5 | name: 'TagSelectOption', 6 | props: { 7 | prefixCls: { 8 | type: String, 9 | default: 'ant-pro-tag-select-option' 10 | }, 11 | value: { 12 | type: [String, Number, Object], 13 | default: '' 14 | }, 15 | checked: { 16 | type: Boolean, 17 | default: false 18 | } 19 | }, 20 | data () { 21 | return { 22 | localChecked: this.checked || false 23 | } 24 | }, 25 | watch: { 26 | 'checked' (val) { 27 | this.localChecked = val 28 | }, 29 | '$parent.items': { 30 | handler: function (val) { 31 | this.value && val.hasOwnProperty(this.value) && (this.localChecked = val[this.value]) 32 | }, 33 | deep: true 34 | } 35 | }, 36 | render () { 37 | const { $slots, value } = this 38 | const onChange = (checked) => { 39 | this.$emit('change', { value, checked }) 40 | } 41 | return ( 42 | {$slots.default} 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/TextArea/index.jsx: -------------------------------------------------------------------------------- 1 | import './style.less' 2 | import { getStrFullLength, cutStrByFullLength } from '../_util/util' 3 | import Input from 'ant-design-vue/es/input' 4 | const TextArea = Input.TextArea 5 | 6 | export default { 7 | name: 'LimitTextArea', 8 | model: { 9 | prop: 'value', 10 | event: 'change' 11 | }, 12 | props: Object.assign({}, TextArea.props, { 13 | prefixCls: { 14 | type: String, 15 | default: 'ant-textarea-limit' 16 | }, 17 | // eslint-disable-next-line 18 | value: { 19 | type: String 20 | }, 21 | limit: { 22 | type: Number, 23 | default: 200 24 | } 25 | }), 26 | data () { 27 | return { 28 | currentLimit: 0 29 | } 30 | }, 31 | watch: { 32 | value (val) { 33 | this.calcLimitNum(val) 34 | } 35 | }, 36 | created () { 37 | this.calcLimitNum(this.value) 38 | }, 39 | methods: { 40 | handleChange (e) { 41 | const value = e.target.value 42 | const len = getStrFullLength(value) 43 | if (len <= this.limit) { 44 | this.currentLimit = len 45 | this.$emit('change', value) 46 | return 47 | } else { 48 | const str = cutStrByFullLength(value, this.limit) 49 | this.currentLimit = getStrFullLength(str) 50 | this.$emit('change', str) 51 | } 52 | console.error('limit out! currentLimit:', this.currentLimit) 53 | }, 54 | calcLimitNum (val) { 55 | const len = getStrFullLength(val) 56 | this.currentLimit = len 57 | } 58 | }, 59 | render () { 60 | const { prefixCls, ...props } = this.$props 61 | return ( 62 |
    63 | 65 | {this.currentLimit}/{this.limit} 66 |
    67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/TextArea/style.less: -------------------------------------------------------------------------------- 1 | .ant-textarea-limit { 2 | position: relative; 3 | 4 | .limit { 5 | position: absolute; 6 | right: 10px; 7 | bottom: 5px; 8 | font-size: 12px; 9 | color: #909399; 10 | background: #fff; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Trend/Trend.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /src/components/Trend/index.js: -------------------------------------------------------------------------------- 1 | import Trend from './Trend.vue' 2 | 3 | export default Trend 4 | -------------------------------------------------------------------------------- /src/components/Trend/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @trend-prefix-cls: ~"@{ant-pro-prefix}-trend"; 4 | 5 | .@{trend-prefix-cls} { 6 | display: inline-block; 7 | font-size: @font-size-base; 8 | line-height: 22px; 9 | 10 | .up, 11 | .down { 12 | position: relative; 13 | top: 1px; 14 | margin-left: 4px; 15 | 16 | i { 17 | font-size: 12px; 18 | transform: scale(.83); 19 | } 20 | } 21 | 22 | .item-text { 23 | display: inline-block; 24 | margin-left: 8px; 25 | color: rgb(0 0 0 / 85%); 26 | } 27 | 28 | .up { 29 | color: @red-6; 30 | } 31 | 32 | .down { 33 | top: -1px; 34 | color: @green-6; 35 | } 36 | 37 | &.reverse-color .up { 38 | color: @green-6; 39 | } 40 | 41 | &.reverse-color .down { 42 | color: @red-6; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Trend/index.md: -------------------------------------------------------------------------------- 1 | # Trend 趋势标记 2 | 3 | 趋势符号,标记上升和下降趋势。通常用绿色代表“好”,红色代表“不好”,股票涨跌场景除外。 4 | 5 | 6 | 7 | 引用方式: 8 | 9 | ```javascript 10 | import Trend from '@/components/Trend' 11 | 12 | export default { 13 | components: { 14 | Trend 15 | } 16 | } 17 | ``` 18 | 19 | 20 | 21 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 22 | 23 | ```html 24 | 5% 25 | ``` 26 | 或 27 | ```html 28 | 29 | 工资 30 | 5% 31 | 32 | ``` 33 | 或 34 | ```html 35 | 5% 36 | ``` 37 | 38 | 39 | ## API 40 | 41 | | 参数 | 说明 | 类型 | 默认值 | 42 | |----------|------------------------------------------|-------------|-------| 43 | | flag | 上升下降标识:`up|down` | string | - | 44 | | reverseColor | 颜色反转 | Boolean | false | 45 | 46 | -------------------------------------------------------------------------------- /src/components/_util/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * components util 3 | */ 4 | 5 | /** 6 | * 清理空值,对象 7 | * @param children 8 | * @returns {*[]} 9 | */ 10 | export function filterEmpty (children = []) { 11 | return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) 12 | } 13 | 14 | /** 15 | * 获取字符串长度,英文字符 长度1,中文字符长度2 16 | * @param {*} str 17 | */ 18 | export const getStrFullLength = (str = '') => 19 | str.split('').reduce((pre, cur) => { 20 | const charCode = cur.charCodeAt(0) 21 | if (charCode >= 0 && charCode <= 128) { 22 | return pre + 1 23 | } 24 | return pre + 2 25 | }, 0) 26 | 27 | /** 28 | * 截取字符串,根据 maxLength 截取后返回 29 | * @param {*} str 30 | * @param {*} maxLength 31 | */ 32 | export const cutStrByFullLength = (str = '', maxLength) => { 33 | let showLength = 0 34 | return str.split('').reduce((pre, cur) => { 35 | const charCode = cur.charCodeAt(0) 36 | if (charCode >= 0 && charCode <= 128) { 37 | showLength += 1 38 | } else { 39 | showLength += 2 40 | } 41 | if (showLength <= maxLength) { 42 | return pre + cur 43 | } 44 | return pre 45 | }, '') 46 | } 47 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | // chart 2 | import Bar from '@/components/Charts/Bar' 3 | import ChartCard from '@/components/Charts/ChartCard' 4 | import Liquid from '@/components/Charts/Liquid' 5 | import MiniArea from '@/components/Charts/MiniArea' 6 | import MiniSmoothArea from '@/components/Charts/MiniSmoothArea' 7 | import MiniBar from '@/components/Charts/MiniBar' 8 | import MiniProgress from '@/components/Charts/MiniProgress' 9 | import Radar from '@/components/Charts/Radar' 10 | import RankList from '@/components/Charts/RankList' 11 | import TransferBar from '@/components/Charts/TransferBar' 12 | import TagCloud from '@/components/Charts/TagCloud' 13 | 14 | // pro components 15 | import AvatarList from '@/components/AvatarList' 16 | import Ellipsis from '@/components/Ellipsis' 17 | import FooterToolbar from '@/components/FooterToolbar' 18 | import NumberInfo from '@/components/NumberInfo' 19 | import Tree from '@/components/Tree/Tree' 20 | import Trend from '@/components/Trend' 21 | import STable from '@/components/Table' 22 | import MultiTab from '@/components/MultiTab' 23 | import IconSelector from '@/components/IconSelector' 24 | import TagSelect from '@/components/TagSelect' 25 | import StandardFormRow from '@/components/StandardFormRow' 26 | import ArticleListContent from '@/components/ArticleListContent' 27 | 28 | import Dialog from '@/components/Dialog' 29 | 30 | export { 31 | AvatarList, 32 | Bar, 33 | ChartCard, 34 | Liquid, 35 | MiniArea, 36 | MiniSmoothArea, 37 | MiniBar, 38 | MiniProgress, 39 | Radar, 40 | TagCloud, 41 | RankList, 42 | TransferBar, 43 | Trend, 44 | Ellipsis, 45 | FooterToolbar, 46 | NumberInfo, 47 | Tree, 48 | STable, 49 | MultiTab, 50 | IconSelector, 51 | TagSelect, 52 | StandardFormRow, 53 | ArticleListContent, 54 | 55 | Dialog 56 | } 57 | -------------------------------------------------------------------------------- /src/components/index.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/lib/style/index'; 2 | 3 | // The prefix to use on all css classes from ant-pro. 4 | @ant-pro-prefix : ant-pro; 5 | @ant-global-sider-zindex : 106; 6 | @ant-global-header-zindex : 105; 7 | -------------------------------------------------------------------------------- /src/components/tools/TwoStepCaptcha.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 83 | 90 | -------------------------------------------------------------------------------- /src/config/defaultSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目默认配置项 3 | * primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage 4 | * navTheme - sidebar theme ['dark', 'light'] 两种主题 5 | * colorWeak - 色盲模式 6 | * layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 7 | * fixedHeader - 固定 Header : boolean 8 | * fixSiderbar - 固定左侧菜单栏 : boolean 9 | * contentWidth - 内容区布局: 流式 | 固定 10 | * 11 | * storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) 12 | * 13 | */ 14 | 15 | export default { 16 | navTheme: 'dark', // theme for nav menu 17 | primaryColor: '#1890ff', // '#F5222D', // primary color of ant design 18 | layout: 'sidemenu', // nav menu position: `sidemenu` or `topmenu` 19 | contentWidth: 'Fluid', // layout of content: `Fluid` or `Fixed`, only works when layout is topmenu 20 | fixedHeader: false, // sticky header 21 | fixSiderbar: false, // sticky siderbar 22 | colorWeak: false, 23 | menu: { 24 | locale: true 25 | }, 26 | title: 'Ant Design Pro', 27 | pwa: false, 28 | iconfontUrl: '', 29 | production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true' 30 | } 31 | -------------------------------------------------------------------------------- /src/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import storage from 'store' 3 | import { 4 | ACCESS_TOKEN, 5 | APP_LANGUAGE, 6 | TOGGLE_CONTENT_WIDTH, 7 | TOGGLE_FIXED_HEADER, 8 | TOGGLE_FIXED_SIDEBAR, TOGGLE_HIDE_HEADER, 9 | TOGGLE_LAYOUT, TOGGLE_NAV_THEME, TOGGLE_WEAK, 10 | TOGGLE_COLOR, TOGGLE_MULTI_TAB 11 | } from '@/store/mutation-types' 12 | import { printANSI } from '@/utils/screenLog' 13 | import defaultSettings from '@/config/defaultSettings' 14 | 15 | export default function Initializer () { 16 | printANSI() // 请自行移除该行. please remove this line 17 | 18 | store.commit(TOGGLE_LAYOUT, storage.get(TOGGLE_LAYOUT, defaultSettings.layout)) 19 | store.commit(TOGGLE_FIXED_HEADER, storage.get(TOGGLE_FIXED_HEADER, defaultSettings.fixedHeader)) 20 | store.commit(TOGGLE_FIXED_SIDEBAR, storage.get(TOGGLE_FIXED_SIDEBAR, defaultSettings.fixSiderbar)) 21 | store.commit(TOGGLE_CONTENT_WIDTH, storage.get(TOGGLE_CONTENT_WIDTH, defaultSettings.contentWidth)) 22 | store.commit(TOGGLE_HIDE_HEADER, storage.get(TOGGLE_HIDE_HEADER, defaultSettings.autoHideHeader)) 23 | store.commit(TOGGLE_NAV_THEME, storage.get(TOGGLE_NAV_THEME, defaultSettings.navTheme)) 24 | store.commit(TOGGLE_WEAK, storage.get(TOGGLE_WEAK, defaultSettings.colorWeak)) 25 | store.commit(TOGGLE_COLOR, storage.get(TOGGLE_COLOR, defaultSettings.primaryColor)) 26 | store.commit(TOGGLE_MULTI_TAB, storage.get(TOGGLE_MULTI_TAB, defaultSettings.multiTab)) 27 | store.commit('SET_TOKEN', storage.get(ACCESS_TOKEN)) 28 | 29 | store.dispatch('setLang', storage.get(APP_LANGUAGE, 'en-US')) 30 | // last step 31 | } 32 | -------------------------------------------------------------------------------- /src/core/directives/action.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | 4 | /** 5 | * Action 权限指令 6 | * 指令用法: 7 | * - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下: 8 | * 添加用户 9 | * 删除用户 10 | * 修改 11 | * 12 | * - 当前用户没有权限时,组件上使用了该指令则会被隐藏 13 | * - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可 14 | * 15 | * @see https://github.com/vueComponent/ant-design-vue-pro/pull/53 16 | */ 17 | const action = Vue.directive('action', { 18 | inserted: function (el, binding, vnode) { 19 | const actionName = binding.arg 20 | const roles = store.getters.roles 21 | const elVal = vnode.context.$route.meta.permission 22 | const permissionId = Object.prototype.toString.call(elVal) === '[object String]' && [elVal] || elVal 23 | roles.permissions.forEach(p => { 24 | if (!permissionId.includes(p.permissionId)) { 25 | return 26 | } 27 | if (p.actionList && !p.actionList.includes(actionName)) { 28 | el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none') 29 | } 30 | }) 31 | } 32 | }) 33 | 34 | export default action 35 | -------------------------------------------------------------------------------- /src/core/icons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom icon list 3 | * All icons are loaded here for easy management 4 | * @see https://vue.ant.design/components/icon/#Custom-Font-Icon 5 | * 6 | * 自定义图标加载表 7 | * 所有图标均从这里加载,方便管理 8 | */ 9 | import bxAnaalyse from '@/assets/icons/bx-analyse.svg?inline' // path to your '*.svg?inline' file. 10 | 11 | export { bxAnaalyse } 12 | -------------------------------------------------------------------------------- /src/core/lazy_use.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // base library 4 | import { 5 | ConfigProvider, 6 | Layout, 7 | Input, 8 | InputNumber, 9 | Button, 10 | Switch, 11 | Radio, 12 | Checkbox, 13 | Select, 14 | Card, 15 | Form, 16 | Row, 17 | Col, 18 | Modal, 19 | Table, 20 | Tabs, 21 | Icon, 22 | Badge, 23 | Popover, 24 | Dropdown, 25 | List, 26 | Avatar, 27 | Breadcrumb, 28 | Steps, 29 | Spin, 30 | Menu, 31 | Drawer, 32 | Tooltip, 33 | Alert, 34 | Tag, 35 | Divider, 36 | DatePicker, 37 | TimePicker, 38 | Upload, 39 | Progress, 40 | Skeleton, 41 | Popconfirm, 42 | PageHeader, 43 | Result, 44 | Statistic, 45 | Descriptions, 46 | Space, 47 | message, 48 | notification 49 | } from 'ant-design-vue' 50 | import Viser from 'viser-vue' 51 | 52 | // ext library 53 | import VueCropper from 'vue-cropper' 54 | import Dialog from '@/components/Dialog' 55 | import MultiTab from '@/components/MultiTab' 56 | import PageLoading from '@/components/PageLoading' 57 | import PermissionHelper from '@/core/permission/permission' 58 | import './directives/action' 59 | 60 | Vue.use(ConfigProvider) 61 | Vue.use(Layout) 62 | Vue.use(Input) 63 | Vue.use(InputNumber) 64 | Vue.use(Button) 65 | Vue.use(Switch) 66 | Vue.use(Radio) 67 | Vue.use(Checkbox) 68 | Vue.use(Select) 69 | Vue.use(Card) 70 | Vue.use(Form) 71 | Vue.use(Row) 72 | Vue.use(Col) 73 | Vue.use(Modal) 74 | Vue.use(Table) 75 | Vue.use(Tabs) 76 | Vue.use(Icon) 77 | Vue.use(Badge) 78 | Vue.use(Popover) 79 | Vue.use(Dropdown) 80 | Vue.use(List) 81 | Vue.use(Avatar) 82 | Vue.use(Breadcrumb) 83 | Vue.use(Steps) 84 | Vue.use(Spin) 85 | Vue.use(Menu) 86 | Vue.use(Drawer) 87 | Vue.use(Tooltip) 88 | Vue.use(Alert) 89 | Vue.use(Tag) 90 | Vue.use(Divider) 91 | Vue.use(DatePicker) 92 | Vue.use(TimePicker) 93 | Vue.use(Upload) 94 | Vue.use(Progress) 95 | Vue.use(Skeleton) 96 | Vue.use(Popconfirm) 97 | Vue.use(PageHeader) 98 | Vue.use(Result) 99 | Vue.use(Statistic) 100 | Vue.use(Descriptions) 101 | Vue.use(Space) 102 | 103 | Vue.prototype.$confirm = Modal.confirm 104 | Vue.prototype.$message = message 105 | Vue.prototype.$notification = notification 106 | Vue.prototype.$info = Modal.info 107 | Vue.prototype.$success = Modal.success 108 | Vue.prototype.$error = Modal.error 109 | Vue.prototype.$warning = Modal.warning 110 | 111 | Vue.use(Viser) 112 | Vue.use(Dialog) // this.$dialog func 113 | Vue.use(MultiTab) 114 | Vue.use(PageLoading) 115 | Vue.use(PermissionHelper) 116 | Vue.use(VueCropper) 117 | 118 | process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] NOTICE: Antd use lazy-load.') 119 | -------------------------------------------------------------------------------- /src/core/permission/permission.js: -------------------------------------------------------------------------------- 1 | export const PERMISSION_ENUM = { 2 | 'add': { key: 'add', label: '新增' }, 3 | 'delete': { key: 'delete', label: '删除' }, 4 | 'edit': { key: 'edit', label: '修改' }, 5 | 'query': { key: 'query', label: '查询' }, 6 | 'get': { key: 'get', label: '详情' }, 7 | 'enable': { key: 'enable', label: '启用' }, 8 | 'disable': { key: 'disable', label: '禁用' }, 9 | 'import': { key: 'import', label: '导入' }, 10 | 'export': { key: 'export', label: '导出' } 11 | } 12 | 13 | /** 14 | * Button 15 | * @param Vue 16 | */ 17 | function plugin (Vue) { 18 | if (plugin.installed) { 19 | return 20 | } 21 | 22 | !Vue.prototype.$auth && Object.defineProperties(Vue.prototype, { 23 | $auth: { 24 | get () { 25 | const _this = this 26 | return (permissions) => { 27 | const [permission, action] = permissions.split('.') 28 | const permissionList = _this.$store.getters.roles.permissions 29 | return permissionList.find((val) => { 30 | return val.permissionId === permission 31 | }).actionList.findIndex((val) => { 32 | return val === action 33 | }) > -1 34 | } 35 | } 36 | } 37 | }) 38 | 39 | !Vue.prototype.$enum && Object.defineProperties(Vue.prototype, { 40 | $enum: { 41 | get () { 42 | // const _this = this; 43 | return (val) => { 44 | let result = PERMISSION_ENUM 45 | val && val.split('.').forEach(v => { 46 | result = result && result[v] || null 47 | }) 48 | return result 49 | } 50 | } 51 | } 52 | }) 53 | } 54 | 55 | export default plugin 56 | -------------------------------------------------------------------------------- /src/core/use.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // base library 4 | import Antd from 'ant-design-vue' 5 | import Viser from 'viser-vue' 6 | import VueCropper from 'vue-cropper' 7 | import 'ant-design-vue/dist/antd.less' 8 | 9 | // ext library 10 | import VueClipboard from 'vue-clipboard2' 11 | import MultiTab from '@/components/MultiTab' 12 | import PageLoading from '@/components/PageLoading' 13 | import PermissionHelper from '@/core/permission/permission' 14 | // import '@/components/use' 15 | import './directives/action' 16 | 17 | VueClipboard.config.autoSetContainer = true 18 | 19 | Vue.use(Antd) 20 | Vue.use(Viser) 21 | Vue.use(MultiTab) 22 | Vue.use(PageLoading) 23 | Vue.use(VueClipboard) 24 | Vue.use(PermissionHelper) 25 | Vue.use(VueCropper) 26 | 27 | process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] WARNING: Antd now use fulled imported.') 28 | -------------------------------------------------------------------------------- /src/global.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default.less'; 2 | 3 | html, 4 | body, 5 | #app, 6 | #root { 7 | height: 100%; 8 | } 9 | 10 | .colorWeak { 11 | filter: invert(80%); 12 | } 13 | 14 | .ant-layout.layout-basic { 15 | height: 100vh; 16 | min-height: 100vh; 17 | } 18 | 19 | canvas { 20 | display: block; 21 | } 22 | 23 | body { 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | text-rendering: optimizelegibility; 27 | } 28 | 29 | ul, 30 | ol { 31 | list-style: none; 32 | } 33 | 34 | // 数据列表 样式 35 | .table-alert { 36 | margin-bottom: 16px; 37 | } 38 | // 数据列表 操作 39 | .table-operator { 40 | margin-bottom: 18px; 41 | 42 | button { 43 | margin-right: 8px; 44 | } 45 | } 46 | // 数据列表 搜索条件 47 | .table-page-search-wrapper { 48 | .ant-form-inline { 49 | .ant-form-item { 50 | display: flex; 51 | margin-right: 0; 52 | margin-bottom: 24px; 53 | 54 | .ant-form-item-control-wrapper { 55 | flex: 1 1; 56 | display: inline-block; 57 | vertical-align: middle; 58 | } 59 | 60 | > .ant-form-item-label { 61 | width: auto; 62 | padding-right: 8px; 63 | line-height: 32px; 64 | } 65 | 66 | .ant-form-item-control { 67 | height: 32px; 68 | line-height: 32px; 69 | } 70 | } 71 | } 72 | 73 | .table-page-search-submitButtons { 74 | display: block; 75 | margin-bottom: 24px; 76 | white-space: nowrap; 77 | } 78 | } 79 | 80 | @media (max-width: @screen-xs) { 81 | .ant-table { 82 | width: 100%; 83 | overflow-x: auto; 84 | 85 | &-thead > tr, 86 | &-tbody > tr { 87 | > th, 88 | > td { 89 | white-space: pre; 90 | 91 | > span { 92 | display: block; 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default.less'; 2 | 3 | .ant-pro-global-header-index-right { 4 | margin-right: 8px; 5 | 6 | &.ant-pro-global-header-index-dark { 7 | .ant-pro-global-header-index-action { 8 | color: hsl(0deg 0% 100% / 85%); 9 | 10 | &:hover { 11 | background: #1890ff; 12 | } 13 | } 14 | } 15 | 16 | .ant-pro-account-avatar { 17 | .antd-pro-global-header-index-avatar { 18 | margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0; 19 | margin-right: 8px; 20 | color: @primary-color; 21 | vertical-align: top; 22 | background: rgb(255 255 255 / 85%); 23 | } 24 | } 25 | 26 | .menu { 27 | .anticon { 28 | margin-right: 8px; 29 | } 30 | 31 | .ant-dropdown-menu-item { 32 | min-width: 100px; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/layouts/BlankLayout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /src/layouts/PageView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /src/layouts/RouteView.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import UserLayout from './UserLayout' 2 | import BlankLayout from './BlankLayout' 3 | import BasicLayout from './BasicLayout' 4 | import RouteView from './RouteView' 5 | import PageView from './PageView' 6 | 7 | export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView } 8 | -------------------------------------------------------------------------------- /src/locales/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import storage from 'store' 4 | import moment from 'moment' 5 | 6 | // default lang 7 | import enUS from './lang/en-US' 8 | 9 | Vue.use(VueI18n) 10 | 11 | export const defaultLang = 'en-US' 12 | 13 | const messages = { 14 | 'en-US': { 15 | ...enUS 16 | } 17 | } 18 | 19 | const i18n = new VueI18n({ 20 | silentTranslationWarn: true, 21 | locale: defaultLang, 22 | fallbackLocale: defaultLang, 23 | messages 24 | }) 25 | 26 | const loadedLanguages = [defaultLang] 27 | 28 | function setI18nLanguage (lang) { 29 | i18n.locale = lang 30 | // request.headers['Accept-Language'] = lang 31 | document.querySelector('html').setAttribute('lang', lang) 32 | return lang 33 | } 34 | 35 | export function loadLanguageAsync (lang = defaultLang) { 36 | return new Promise(resolve => { 37 | // 缓存语言设置 38 | storage.set('lang', lang) 39 | if (i18n.locale !== lang) { 40 | if (!loadedLanguages.includes(lang)) { 41 | return import(/* webpackChunkName: "lang-[request]" */ `./lang/${lang}`).then(msg => { 42 | const locale = msg.default 43 | i18n.setLocaleMessage(lang, locale) 44 | loadedLanguages.push(lang) 45 | moment.updateLocale(locale.momentName, locale.momentLocale) 46 | return setI18nLanguage(lang) 47 | }) 48 | } 49 | return resolve(setI18nLanguage(lang)) 50 | } 51 | return resolve(lang) 52 | }) 53 | } 54 | 55 | export function i18nRender (key) { 56 | return i18n.t(`${key}`) 57 | } 58 | 59 | export default i18n 60 | -------------------------------------------------------------------------------- /src/locales/lang/en-US.js: -------------------------------------------------------------------------------- 1 | import antdEnUS from 'ant-design-vue/es/locale-provider/en_US' 2 | import momentEU from 'moment/locale/eu' 3 | import global from './en-US/global' 4 | 5 | import menu from './en-US/menu' 6 | import setting from './en-US/setting' 7 | import user from './en-US/user' 8 | 9 | import dashboard from './en-US/dashboard' 10 | import form from './en-US/form' 11 | import result from './en-US/result' 12 | import account from './en-US/account' 13 | 14 | const components = { 15 | antLocale: antdEnUS, 16 | momentName: 'eu', 17 | momentLocale: momentEU 18 | } 19 | 20 | export default { 21 | message: '-', 22 | 23 | 'layouts.usermenu.dialog.title': 'Message', 24 | 'layouts.usermenu.dialog.content': 'Are you sure you would like to logout?', 25 | 'layouts.userLayout.title': 'Ant Design is the most influential web design specification in Xihu district', 26 | ...components, 27 | ...global, 28 | ...menu, 29 | ...setting, 30 | ...user, 31 | ...dashboard, 32 | ...form, 33 | ...result, 34 | ...account 35 | } 36 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/account.js: -------------------------------------------------------------------------------- 1 | import settings from './account/settings' 2 | 3 | export default { 4 | ...settings 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/dashboard.js: -------------------------------------------------------------------------------- 1 | import analysis from './dashboard/analysis' 2 | 3 | export default { 4 | ...analysis 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/dashboard/analysis.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboard.analysis.test': 'Gongzhuan No.{no} shop', 3 | 'dashboard.analysis.introduce': 'Introduce', 4 | 'dashboard.analysis.total-sales': 'Total Sales', 5 | 'dashboard.analysis.day-sales': 'Daily Sales', 6 | 'dashboard.analysis.visits': 'Visits', 7 | 'dashboard.analysis.visits-trend': 'Visits Trend', 8 | 'dashboard.analysis.visits-ranking': 'Visits Ranking', 9 | 'dashboard.analysis.day-visits': 'Daily Visits', 10 | 'dashboard.analysis.week': 'WoW Change', 11 | 'dashboard.analysis.day': 'DoD Change', 12 | 'dashboard.analysis.payments': 'Payments', 13 | 'dashboard.analysis.conversion-rate': 'Conversion Rate', 14 | 'dashboard.analysis.operational-effect': 'Operational Effect', 15 | 'dashboard.analysis.sales-trend': 'Stores Sales Trend', 16 | 'dashboard.analysis.sales-ranking': 'Sales Ranking', 17 | 'dashboard.analysis.all-year': 'All Year', 18 | 'dashboard.analysis.all-month': 'All Month', 19 | 'dashboard.analysis.all-week': 'All Week', 20 | 'dashboard.analysis.all-day': 'All day', 21 | 'dashboard.analysis.search-users': 'Search Users', 22 | 'dashboard.analysis.per-capita-search': 'Per Capita Search', 23 | 'dashboard.analysis.online-top-search': 'Online Top Search', 24 | 'dashboard.analysis.the-proportion-of-sales': 'The Proportion Of Sales', 25 | 'dashboard.analysis.dropdown-option-one': 'Operation one', 26 | 'dashboard.analysis.dropdown-option-two': 'Operation two', 27 | 'dashboard.analysis.channel.all': 'ALL', 28 | 'dashboard.analysis.channel.online': 'Online', 29 | 'dashboard.analysis.channel.stores': 'Stores', 30 | 'dashboard.analysis.sales': 'Sales', 31 | 'dashboard.analysis.traffic': 'Traffic', 32 | 'dashboard.analysis.table.rank': 'Rank', 33 | 'dashboard.analysis.table.search-keyword': 'Keyword', 34 | 'dashboard.analysis.table.users': 'Users', 35 | 'dashboard.analysis.table.weekly-range': 'Weekly Range' 36 | } 37 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/form.js: -------------------------------------------------------------------------------- 1 | import basicForm from './form/basicForm' 2 | 3 | export default { 4 | ...basicForm 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/global.js: -------------------------------------------------------------------------------- 1 | export default { 2 | submit: 'Submit', 3 | save: 'Save', 4 | 'submit.ok': 'Submit successfully', 5 | 'save.ok': 'Saved successfully' 6 | } 7 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': 'Welcome', 3 | 'menu.home': 'Home', 4 | 'menu.dashboard': 'Dashboard', 5 | 'menu.dashboard.analysis': 'Analysis', 6 | 'menu.dashboard.monitor': 'Monitor', 7 | 'menu.dashboard.workplace': 'Workplace', 8 | 'menu.form': 'Form', 9 | 'menu.form.basic-form': 'Basic Form', 10 | 'menu.form.step-form': 'Step Form', 11 | 'menu.form.step-form.info': 'Step Form(write transfer information)', 12 | 'menu.form.step-form.confirm': 'Step Form(confirm transfer information)', 13 | 'menu.form.step-form.result': 'Step Form(finished)', 14 | 'menu.form.advanced-form': 'Advanced Form', 15 | 'menu.list': 'List', 16 | 'menu.list.table-list': 'Search Table', 17 | 'menu.list.basic-list': 'Basic List', 18 | 'menu.list.card-list': 'Card List', 19 | 'menu.list.search-list': 'Search List', 20 | 'menu.list.search-list.articles': 'Search List(articles)', 21 | 'menu.list.search-list.projects': 'Search List(projects)', 22 | 'menu.list.search-list.applications': 'Search List(applications)', 23 | 'menu.profile': 'Profile', 24 | 'menu.profile.basic': 'Basic Profile', 25 | 'menu.profile.advanced': 'Advanced Profile', 26 | 'menu.result': 'Result', 27 | 'menu.result.success': 'Success', 28 | 'menu.result.fail': 'Fail', 29 | 'menu.exception': 'Exception', 30 | 'menu.exception.not-permission': '403', 31 | 'menu.exception.not-find': '404', 32 | 'menu.exception.server-error': '500', 33 | 'menu.exception.trigger': 'Trigger', 34 | 'menu.account': 'Account', 35 | 'menu.account.center': 'Account Center', 36 | 'menu.account.settings': 'Account Settings', 37 | 'menu.account.trigger': 'Trigger Error', 38 | 'menu.account.logout': 'Logout' 39 | } 40 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/result.js: -------------------------------------------------------------------------------- 1 | import success from './result/success' 2 | import fail from './result/fail' 3 | 4 | export default { 5 | ...success, 6 | ...fail 7 | } 8 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/result/fail.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.fail.error.title': 'Submission Failed', 3 | 'result.fail.error.description': 4 | 'Please check and modify the following information before resubmitting.', 5 | 'result.fail.error.hint-title': 'The content you submitted has the following error:', 6 | 'result.fail.error.hint-text1': 'Your account has been frozen', 7 | 'result.fail.error.hint-btn1': 'Thaw immediately', 8 | 'result.fail.error.hint-text2': 'Your account is not yet eligible to apply', 9 | 'result.fail.error.hint-btn2': 'Upgrade immediately', 10 | 'result.fail.error.btn-text': 'Return to modify' 11 | } 12 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/result/success.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.success.title': 'Submission Success', 3 | 'result.success.description': 4 | 'The submission results page is used to feed back the results of a series of operational tasks. If it is a simple operation, use the Message global prompt feedback. This text area can show a simple supplementary explanation. If there is a similar requirement for displaying “documents”, the following gray area can present more complicated content.', 5 | 'result.success.operate-title': 'Project Name', 6 | 'result.success.operate-id': 'Project ID', 7 | 'result.success.principal': 'Principal', 8 | 'result.success.operate-time': 'Effective time', 9 | 'result.success.step1-title': 'Create project', 10 | 'result.success.step1-operator': 'Qu Lili', 11 | 'result.success.step2-title': 'Departmental preliminary review', 12 | 'result.success.step2-operator': 'Zhou Maomao', 13 | 'result.success.step2-extra': 'Urge', 14 | 'result.success.step3-title': 'Financial review', 15 | 'result.success.step4-title': 'Finish', 16 | 'result.success.btn-return': 'Back List', 17 | 'result.success.btn-project': 'View Project', 18 | 'result.success.btn-print': 'Print' 19 | } 20 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/setting.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Page style setting', 3 | 'app.setting.pagestyle.light': 'Light style', 4 | 'app.setting.pagestyle.dark': 'Dark style', 5 | 'app.setting.pagestyle.realdark': 'RealDark style', 6 | 'app.setting.themecolor': 'Theme Color', 7 | 'app.setting.navigationmode': 'Navigation Mode', 8 | 'app.setting.content-width': 'Content Width', 9 | 'app.setting.fixedheader': 'Fixed Header', 10 | 'app.setting.fixedsidebar': 'Fixed Sidebar', 11 | 'app.setting.sidemenu': 'Side Menu Layout', 12 | 'app.setting.topmenu': 'Top Menu Layout', 13 | 'app.setting.content-width.fixed': 'Fixed', 14 | 'app.setting.content-width.fluid': 'Fluid', 15 | 'app.setting.othersettings': 'Other Settings', 16 | 'app.setting.weakmode': 'Weak Mode', 17 | 'app.setting.copy': 'Copy Setting', 18 | 'app.setting.loading': 'Loading theme', 19 | 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/config/defaultSettings.js', 20 | 'app.setting.production.hint': 'Setting panel shows in development environment only, please manually modify', 21 | 'app.setting.themecolor.daybreak': 'Daybreak Blue', 22 | 'app.setting.themecolor.dust': 'Dust Red', 23 | 'app.setting.themecolor.volcano': 'Volcano', 24 | 'app.setting.themecolor.sunset': 'Sunset Orange', 25 | 'app.setting.themecolor.cyan': 'Cyan', 26 | 'app.setting.themecolor.green': 'Polar Green', 27 | 'app.setting.themecolor.geekblue': 'Geek Blue', 28 | 'app.setting.themecolor.purple': 'Golden Purple' 29 | } 30 | -------------------------------------------------------------------------------- /src/locales/lang/en-US/user.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'user.login.userName': 'userName', 3 | 'user.login.password': 'password', 4 | 'user.login.username.placeholder': 'Account: admin', 5 | 'user.login.password.placeholder': 'password: admin or ant.design', 6 | 'user.login.message-invalid-credentials': 7 | 'Invalid username or password(admin/ant.design)', 8 | 'user.login.message-invalid-verification-code': 'Invalid verification code', 9 | 'user.login.tab-login-credentials': 'Credentials', 10 | 'user.login.tab-login-mobile': 'Mobile number', 11 | 'user.login.mobile.placeholder': 'Mobile number', 12 | 'user.login.mobile.verification-code.placeholder': 'Verification code', 13 | 'user.login.remember-me': 'Remember me', 14 | 'user.login.forgot-password': 'Forgot your password?', 15 | 'user.login.sign-in-with': 'Sign in with', 16 | 'user.login.signup': 'Sign up', 17 | 'user.login.login': 'Login', 18 | 'user.register.register': 'Register', 19 | 'user.register.email.placeholder': 'Email', 20 | 'user.register.password.placeholder': 'Password ', 21 | 'user.register.password.popover-message': 'Please enter at least 6 characters. Please do not use passwords that are easy to guess. ', 22 | 'user.register.confirm-password.placeholder': 'Confirm password', 23 | 'user.register.get-verification-code': 'Get code', 24 | 'user.register.sign-in': 'Already have an account?', 25 | 'user.register-result.msg': 'Account:registered at {email}', 26 | 'user.register-result.activation-email': 27 | 'The activation email has been sent to your email address and is valid for 24 hours. Please log in to the email in time and click on the link in the email to activate the account.', 28 | 'user.register-result.back-home': 'Back to home', 29 | 'user.register-result.view-mailbox': 'View mailbox', 30 | 'user.email.required': 'Please enter your email!', 31 | 'user.email.wrong-format': 'The email address is in the wrong format!', 32 | 'user.userName.required': 'Please enter account name or email address', 33 | 'user.password.required': 'Please enter your password!', 34 | 'user.password.twice.msg': 'The passwords entered twice do not match!', 35 | 'user.password.strength.msg': 36 | 'The password is not strong enough', 37 | 'user.password.strength.strong': 'Strength: strong', 38 | 'user.password.strength.medium': 'Strength: medium', 39 | 'user.password.strength.low': 'Strength: low', 40 | 'user.password.strength.short': 'Strength: too short', 41 | 'user.confirm-password.required': 'Please confirm your password!', 42 | 'user.phone-number.required': 'Please enter your phone number!', 43 | 'user.phone-number.wrong-format': 'Please enter a valid phone number', 44 | 'user.verification-code.required': 'Please enter the verification code!' 45 | } 46 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | import antd from 'ant-design-vue/es/locale-provider/zh_CN' 2 | import momentCN from 'moment/locale/zh-cn' 3 | import global from './zh-CN/global' 4 | 5 | import menu from './zh-CN/menu' 6 | import setting from './zh-CN/setting' 7 | import user from './zh-CN/user' 8 | import dashboard from './zh-CN/dashboard' 9 | import form from './zh-CN/form' 10 | import result from './zh-CN/result' 11 | import account from './zh-CN/account' 12 | 13 | const components = { 14 | antLocale: antd, 15 | momentName: 'zh-cn', 16 | momentLocale: momentCN 17 | } 18 | 19 | export default { 20 | message: '-', 21 | 22 | 'layouts.usermenu.dialog.title': '信息', 23 | 'layouts.usermenu.dialog.content': '您确定要注销吗?', 24 | 'layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范', 25 | ...components, 26 | ...global, 27 | ...menu, 28 | ...setting, 29 | ...user, 30 | ...dashboard, 31 | ...form, 32 | ...result, 33 | ...account 34 | } 35 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/account.js: -------------------------------------------------------------------------------- 1 | import settings from './account/settings' 2 | 3 | export default { 4 | ...settings 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/account/settings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'account.settings.menuMap.basic': '基本设置', 3 | 'account.settings.menuMap.security': '安全设置', 4 | 'account.settings.menuMap.custom': '个性化', 5 | 'account.settings.menuMap.binding': '账号绑定', 6 | 'account.settings.menuMap.notification': '新消息通知', 7 | 'account.settings.basic.avatar': '头像', 8 | 'account.settings.basic.change-avatar': '更换头像', 9 | 'account.settings.basic.email': '邮箱', 10 | 'account.settings.basic.email-message': '请输入您的邮箱!', 11 | 'account.settings.basic.nickname': '昵称', 12 | 'account.settings.basic.nickname-message': '请输入您的昵称!', 13 | 'account.settings.basic.profile': '个人简介', 14 | 'account.settings.basic.profile-message': '请输入个人简介!', 15 | 'account.settings.basic.profile-placeholder': '个人简介', 16 | 'account.settings.basic.country': '国家/地区', 17 | 'account.settings.basic.country-message': '请输入您的国家或地区!', 18 | 'account.settings.basic.geographic': '所在省市', 19 | 'account.settings.basic.geographic-message': '请输入您的所在省市!', 20 | 'account.settings.basic.address': '街道地址', 21 | 'account.settings.basic.address-message': '请输入您的街道地址!', 22 | 'account.settings.basic.phone': '联系电话', 23 | 'account.settings.basic.phone-message': '请输入您的联系电话!', 24 | 'account.settings.basic.update': '更新基本信息', 25 | 'account.settings.basic.update.success': '更新基本信息成功', 26 | 'account.settings.security.strong': '强', 27 | 'account.settings.security.medium': '中', 28 | 'account.settings.security.weak': '弱', 29 | 'account.settings.security.password': '账户密码', 30 | 'account.settings.security.password-description': '当前密码强度:', 31 | 'account.settings.security.phone': '密保手机', 32 | 'account.settings.security.phone-description': '已绑定手机:', 33 | 'account.settings.security.question': '密保问题', 34 | 'account.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全', 35 | 'account.settings.security.email': '备用邮箱', 36 | 'account.settings.security.email-description': '已绑定邮箱:', 37 | 'account.settings.security.mfa': 'MFA 设备', 38 | 'account.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认', 39 | 'account.settings.security.modify': '修改', 40 | 'account.settings.security.set': '设置', 41 | 'account.settings.security.bind': '绑定', 42 | 'account.settings.binding.taobao': '绑定淘宝', 43 | 'account.settings.binding.taobao-description': '当前未绑定淘宝账号', 44 | 'account.settings.binding.alipay': '绑定支付宝', 45 | 'account.settings.binding.alipay-description': '当前未绑定支付宝账号', 46 | 'account.settings.binding.dingding': '绑定钉钉', 47 | 'account.settings.binding.dingding-description': '当前未绑定钉钉账号', 48 | 'account.settings.binding.bind': '绑定', 49 | 'account.settings.notification.password': '账户密码', 50 | 'account.settings.notification.password-description': '其他用户的消息将以站内信的形式通知', 51 | 'account.settings.notification.messages': '系统消息', 52 | 'account.settings.notification.messages-description': '系统消息将以站内信的形式通知', 53 | 'account.settings.notification.todo': '待办任务', 54 | 'account.settings.notification.todo-description': '待办任务将以站内信的形式通知', 55 | 'account.settings.settings.open': '开', 56 | 'account.settings.settings.close': '关' 57 | } 58 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/dashboard.js: -------------------------------------------------------------------------------- 1 | import analysis from './dashboard/analysis' 2 | 3 | export default { 4 | ...analysis 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/dashboard/analysis.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboard.analysis.test': '工专路 {no} 号店', 3 | 'dashboard.analysis.introduce': '指标说明', 4 | 'dashboard.analysis.total-sales': '总销售额', 5 | 'dashboard.analysis.day-sales': '日均销售额¥', 6 | 'dashboard.analysis.visits': '访问量', 7 | 'dashboard.analysis.visits-trend': '访问量趋势', 8 | 'dashboard.analysis.visits-ranking': '门店访问量排名', 9 | 'dashboard.analysis.day-visits': '日访问量', 10 | 'dashboard.analysis.week': '周同比', 11 | 'dashboard.analysis.day': '日同比', 12 | 'dashboard.analysis.payments': '支付笔数', 13 | 'dashboard.analysis.conversion-rate': '转化率', 14 | 'dashboard.analysis.operational-effect': '运营活动效果', 15 | 'dashboard.analysis.sales-trend': '销售趋势', 16 | 'dashboard.analysis.sales-ranking': '门店销售额排名', 17 | 'dashboard.analysis.all-year': '全年', 18 | 'dashboard.analysis.all-month': '本月', 19 | 'dashboard.analysis.all-week': '本周', 20 | 'dashboard.analysis.all-day': '今日', 21 | 'dashboard.analysis.search-users': '搜索用户数', 22 | 'dashboard.analysis.per-capita-search': '人均搜索次数', 23 | 'dashboard.analysis.online-top-search': '线上热门搜索', 24 | 'dashboard.analysis.the-proportion-of-sales': '销售额类别占比', 25 | 'dashboard.analysis.dropdown-option-one': '操作一', 26 | 'dashboard.analysis.dropdown-option-two': '操作二', 27 | 'dashboard.analysis.channel.all': '全部渠道', 28 | 'dashboard.analysis.channel.online': '线上', 29 | 'dashboard.analysis.channel.stores': '门店', 30 | 'dashboard.analysis.sales': '销售额', 31 | 'dashboard.analysis.traffic': '客流量', 32 | 'dashboard.analysis.table.rank': '排名', 33 | 'dashboard.analysis.table.search-keyword': '搜索关键词', 34 | 'dashboard.analysis.table.users': '用户数', 35 | 'dashboard.analysis.table.weekly-range': '周涨幅' 36 | } 37 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/form.js: -------------------------------------------------------------------------------- 1 | import basicForm from './form/basicForm' 2 | 3 | export default { 4 | ...basicForm 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/form/basicForm.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'form.basic-form.basic.title': '基础表单', 3 | 'form.basic-form.basic.description': 4 | '表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。', 5 | 'form.basic-form.title.label': '标题', 6 | 'form.basic-form.title.placeholder': '给目标起个名字', 7 | 'form.basic-form.title.required': '请输入标题', 8 | 'form.basic-form.date.label': '起止日期', 9 | 'form.basic-form.placeholder.start': '开始日期', 10 | 'form.basic-form.placeholder.end': '结束日期', 11 | 'form.basic-form.date.required': '请选择起止日期', 12 | 'form.basic-form.goal.label': '目标描述', 13 | 'form.basic-form.goal.placeholder': '请输入你的阶段性工作目标', 14 | 'form.basic-form.goal.required': '请输入目标描述', 15 | 'form.basic-form.standard.label': '衡量标准', 16 | 'form.basic-form.standard.placeholder': '请输入衡量标准', 17 | 'form.basic-form.standard.required': '请输入衡量标准', 18 | 'form.basic-form.client.label': '客户', 19 | 'form.basic-form.client.required': '请描述你服务的客户', 20 | 'form.basic-form.label.tooltip': '目标的服务对象', 21 | 'form.basic-form.client.placeholder': '请描述你服务的客户,内部客户直接 @姓名/工号', 22 | 'form.basic-form.invites.label': '邀评人', 23 | 'form.basic-form.invites.placeholder': '请直接 @姓名/工号,最多可邀请 5 人', 24 | 'form.basic-form.weight.label': '权重', 25 | 'form.basic-form.weight.placeholder': '请输入', 26 | 'form.basic-form.public.label': '目标公开', 27 | 'form.basic-form.label.help': '客户、邀评人默认被分享', 28 | 'form.basic-form.radio.public': '公开', 29 | 'form.basic-form.radio.partially-public': '部分公开', 30 | 'form.basic-form.radio.private': '不公开', 31 | 'form.basic-form.publicUsers.placeholder': '公开给', 32 | 'form.basic-form.option.A': '同事一', 33 | 'form.basic-form.option.B': '同事二', 34 | 'form.basic-form.option.C': '同事三', 35 | 'form.basic-form.email.required': '请输入邮箱地址!', 36 | 'form.basic-form.email.wrong-format': '邮箱地址格式错误!', 37 | 'form.basic-form.userName.required': '请输入用户名!', 38 | 'form.basic-form.password.required': '请输入密码!', 39 | 'form.basic-form.password.twice': '两次输入的密码不匹配!', 40 | 'form.basic-form.strength.msg': '请至少输入 6 个字符。请不要使用容易被猜到的密码。', 41 | 'form.basic-form.strength.strong': '强度:强', 42 | 'form.basic-form.strength.medium': '强度:中', 43 | 'form.basic-form.strength.short': '强度:太短', 44 | 'form.basic-form.confirm-password.required': '请确认密码!', 45 | 'form.basic-form.phone-number.required': '请输入手机号!', 46 | 'form.basic-form.phone-number.wrong-format': '手机号格式错误!', 47 | 'form.basic-form.verification-code.required': '请输入验证码!', 48 | 'form.basic-form.form.get-captcha': '获取验证码', 49 | 'form.basic-form.captcha.second': '秒', 50 | 'form.basic-form.form.optional': '(选填)', 51 | 'form.basic-form.form.submit': '提交', 52 | 'form.basic-form.form.save': '保存', 53 | 'form.basic-form.email.placeholder': '邮箱', 54 | 'form.basic-form.password.placeholder': '至少6位密码,区分大小写', 55 | 'form.basic-form.confirm-password.placeholder': '确认密码', 56 | 'form.basic-form.phone-number.placeholder': '手机号', 57 | 'form.basic-form.verification-code.placeholder': '验证码' 58 | } 59 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/global.js: -------------------------------------------------------------------------------- 1 | export default { 2 | submit: '提交', 3 | save: '保存', 4 | 'submit.ok': '提交成功', 5 | 'save.ok': '保存成功' 6 | } 7 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '欢迎', 3 | 'menu.home': '主页', 4 | 'menu.dashboard': '仪表盘', 5 | 'menu.dashboard.analysis': '分析页', 6 | 'menu.dashboard.monitor': '监控页', 7 | 'menu.dashboard.workplace': '工作台', 8 | 'menu.form': '表单页', 9 | 'menu.form.basic-form': '基础表单', 10 | 'menu.form.step-form': '分步表单', 11 | 'menu.form.step-form.info': '分步表单(填写转账信息)', 12 | 'menu.form.step-form.confirm': '分步表单(确认转账信息)', 13 | 'menu.form.step-form.result': '分步表单(完成)', 14 | 'menu.form.advanced-form': '高级表单', 15 | 'menu.list': '列表页', 16 | 'menu.list.table-list': '查询表格', 17 | 'menu.list.basic-list': '标准列表', 18 | 'menu.list.card-list': '卡片列表', 19 | 'menu.list.search-list': '搜索列表', 20 | 'menu.list.search-list.articles': '搜索列表(文章)', 21 | 'menu.list.search-list.projects': '搜索列表(项目)', 22 | 'menu.list.search-list.applications': '搜索列表(应用)', 23 | 'menu.profile': '详情页', 24 | 'menu.profile.basic': '基础详情页', 25 | 'menu.profile.advanced': '高级详情页', 26 | 'menu.result': '结果页', 27 | 'menu.result.success': '成功页', 28 | 'menu.result.fail': '失败页', 29 | 'menu.exception': '异常页', 30 | 'menu.exception.not-permission': '403', 31 | 'menu.exception.not-find': '404', 32 | 'menu.exception.server-error': '500', 33 | 'menu.exception.trigger': '触发错误', 34 | 'menu.account': '个人页', 35 | 'menu.account.center': '个人中心', 36 | 'menu.account.settings': '个人设置', 37 | 'menu.account.trigger': '触发报错', 38 | 'menu.account.logout': '退出登录' 39 | } 40 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/result.js: -------------------------------------------------------------------------------- 1 | import success from './result/success' 2 | import fail from './result/fail' 3 | 4 | export default { 5 | ...success, 6 | ...fail 7 | } 8 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/result/fail.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.fail.error.title': '提交失败', 3 | 'result.fail.error.description': '请核对并修改以下信息后,再重新提交。', 4 | 'result.fail.error.hint-title': '您提交的内容有如下错误:', 5 | 'result.fail.error.hint-text1': '您的账户已被冻结', 6 | 'result.fail.error.hint-btn1': '立即解冻', 7 | 'result.fail.error.hint-text2': '您的账户还不具备申请资格', 8 | 'result.fail.error.hint-btn2': '立即升级', 9 | 'result.fail.error.btn-text': '返回修改' 10 | } 11 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/result/success.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.success.title': '提交成功', 3 | 'result.success.description': 4 | '提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 Message 全局提示反馈即可。 本文字区域可以展示简单的补充说明,如果有类似展示 “单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。', 5 | 'result.success.operate-title': '项目名称', 6 | 'result.success.operate-id': '项目 ID', 7 | 'result.success.principal': '负责人', 8 | 'result.success.operate-time': '生效时间', 9 | 'result.success.step1-title': '创建项目', 10 | 'result.success.step1-operator': '曲丽丽', 11 | 'result.success.step2-title': '部门初审', 12 | 'result.success.step2-operator': '周毛毛', 13 | 'result.success.step2-extra': '催一下', 14 | 'result.success.step3-title': '财务复核', 15 | 'result.success.step4-title': '完成', 16 | 'result.success.btn-return': '返回列表', 17 | 'result.success.btn-project': '查看项目', 18 | 'result.success.btn-print': '打印' 19 | } 20 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/setting.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整体风格设置', 3 | 'app.setting.pagestyle.light': '亮色菜单风格', 4 | 'app.setting.pagestyle.dark': '暗色菜单风格', 5 | 'app.setting.pagestyle.realdark': '暗黑模式', 6 | 'app.setting.themecolor': '主题色', 7 | 'app.setting.navigationmode': '导航模式', 8 | 'app.setting.content-width': '内容区域宽度', 9 | 'app.setting.fixedheader': '固定 Header', 10 | 'app.setting.fixedsidebar': '固定侧边栏', 11 | 'app.setting.sidemenu': '侧边菜单布局', 12 | 'app.setting.topmenu': '顶部菜单布局', 13 | 'app.setting.content-width.fixed': 'Fixed', 14 | 'app.setting.content-width.fluid': 'Fluid', 15 | 'app.setting.othersettings': '其他设置', 16 | 'app.setting.weakmode': '色弱模式', 17 | 'app.setting.copy': '拷贝设置', 18 | 'app.setting.loading': '加载主题中', 19 | 'app.setting.copyinfo': '拷贝设置成功 src/config/defaultSettings.js', 20 | 'app.setting.production.hint': '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件', 21 | 'app.setting.themecolor.daybreak': '拂晓蓝', 22 | 'app.setting.themecolor.dust': '薄暮', 23 | 'app.setting.themecolor.volcano': '火山', 24 | 'app.setting.themecolor.sunset': '日暮', 25 | 'app.setting.themecolor.cyan': '明青', 26 | 'app.setting.themecolor.green': '极光绿', 27 | 'app.setting.themecolor.geekblue': '极客蓝', 28 | 'app.setting.themecolor.purple': '酱紫' 29 | } 30 | -------------------------------------------------------------------------------- /src/locales/lang/zh-CN/user.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'user.login.userName': '用户名', 3 | 'user.login.password': '密码', 4 | 'user.login.username.placeholder': '账户: admin', 5 | 'user.login.password.placeholder': '密码: admin or ant.design', 6 | 'user.login.message-invalid-credentials': '账户或密码错误(admin/ant.design)', 7 | 'user.login.message-invalid-verification-code': '验证码错误', 8 | 'user.login.tab-login-credentials': '账户密码登录', 9 | 'user.login.tab-login-mobile': '手机号登录', 10 | 'user.login.mobile.placeholder': '手机号', 11 | 'user.login.mobile.verification-code.placeholder': '验证码', 12 | 'user.login.remember-me': '自动登录', 13 | 'user.login.forgot-password': '忘记密码', 14 | 'user.login.sign-in-with': '其他登录方式', 15 | 'user.login.signup': '注册账户', 16 | 'user.login.login': '登录', 17 | 'user.register.register': '注册', 18 | 'user.register.email.placeholder': '邮箱', 19 | 'user.register.password.placeholder': '请至少输入 6 个字符。请不要使用容易被猜到的密码。', 20 | 'user.register.password.popover-message': '请至少输入 6 个字符。请不要使用容易被猜到的密码。', 21 | 'user.register.confirm-password.placeholder': '确认密码', 22 | 'user.register.get-verification-code': '获取验证码', 23 | 'user.register.sign-in': '使用已有账户登录', 24 | 'user.register-result.msg': '你的账户:{email} 注册成功', 25 | 'user.register-result.activation-email': 26 | '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。', 27 | 'user.register-result.back-home': '返回首页', 28 | 'user.register-result.view-mailbox': '查看邮箱', 29 | 'user.email.required': '请输入邮箱地址!', 30 | 'user.email.wrong-format': '邮箱地址格式错误!', 31 | 'user.userName.required': '请输入帐户名或邮箱地址', 32 | 'user.password.required': '请输入密码!', 33 | 'user.password.twice.msg': '两次输入的密码不匹配!', 34 | 'user.password.strength.msg': '密码强度不够 ', 35 | 'user.password.strength.strong': '强度:强', 36 | 'user.password.strength.medium': '强度:中', 37 | 'user.password.strength.low': '强度:低', 38 | 'user.password.strength.short': '强度:太短', 39 | 'user.confirm-password.required': '请确认密码!', 40 | 'user.phone-number.required': '请输入正确的手机号', 41 | 'user.phone-number.wrong-format': '手机号格式错误!', 42 | 'user.verification-code.required': '请输入验证码!' 43 | } 44 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // with polyfills 2 | import 'core-js/stable' 3 | import 'regenerator-runtime/runtime' 4 | 5 | import Vue from 'vue' 6 | import App from './App.vue' 7 | import router from './router' 8 | import store from './store/' 9 | import i18n from './locales' 10 | import { VueAxios } from './utils/request' 11 | import ProLayout, { PageHeaderWrapper } from '@ant-design-vue/pro-layout' 12 | import themePluginConfig from '../config/themePluginConfig' 13 | 14 | // mock 15 | // WARNING: `mockjs` NOT SUPPORT `IE` PLEASE DO NOT USE IN `production` ENV. 16 | import './mock' 17 | 18 | import bootstrap from './core/bootstrap' 19 | import './core/lazy_use' // use lazy load components 20 | import './permission' // permission control 21 | import './utils/filter' // global filter 22 | import './global.less' // global style 23 | 24 | Vue.config.productionTip = false 25 | 26 | // mount axios to `Vue.$http` and `this.$http` 27 | Vue.use(VueAxios) 28 | // use pro-layout components 29 | Vue.component('pro-layout', ProLayout) 30 | Vue.component('page-container', PageHeaderWrapper) 31 | Vue.component('page-header-wrapper', PageHeaderWrapper) 32 | 33 | window.umi_plugin_ant_themeVar = themePluginConfig.theme 34 | 35 | new Vue({ 36 | router, 37 | store, 38 | i18n, 39 | // init localstorage, vuex, Logo message 40 | created: bootstrap, 41 | render: h => h(App) 42 | }).$mount('#app') 43 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | import { isIE } from '@/utils/util' 2 | 3 | // 判断环境不是 prod 或者 preview 是 true 时,加载 mock 服务 4 | if (process.env.NODE_ENV !== 'production' || process.env.VUE_APP_PREVIEW === 'true') { 5 | if (isIE()) { 6 | console.error('[antd-pro] ERROR: `mockjs` NOT SUPPORT `IE` PLEASE DO NOT USE IN `production` ENV.') 7 | } 8 | // 使用同步加载依赖 9 | // 防止 vuex 中的 GetInfo 早于 mock 运行,导致无法 mock 请求返回结果 10 | console.log('[antd-pro] mock mounting') 11 | const Mock = require('mockjs2') 12 | require('./services/auth') 13 | require('./services/user') 14 | require('./services/manage') 15 | require('./services/other') 16 | require('./services/tagCloud') 17 | require('./services/article') 18 | 19 | Mock.setup({ 20 | timeout: 800 // setter delay time 21 | }) 22 | console.log('[antd-pro] mock mounted') 23 | } 24 | -------------------------------------------------------------------------------- /src/mock/services/article.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs2' 2 | import { builder, getQueryParameters } from '../util' 3 | 4 | const titles = [ 5 | 'Alipay', 6 | 'Angular', 7 | 'Ant Design', 8 | 'Ant Design Pro', 9 | 'Bootstrap', 10 | 'React', 11 | 'Vue', 12 | 'Webpack' 13 | ] 14 | 15 | const avatar = ['https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', 16 | 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', 17 | 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', 18 | 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', 19 | 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png' 20 | ] 21 | 22 | const covers = [ 23 | 'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png', 24 | 'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png', 25 | 'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png', 26 | 'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png' 27 | ] 28 | 29 | const owner = [ 30 | '付小小', 31 | '吴加好', 32 | '周星星', 33 | '林东东', 34 | '曲丽丽' 35 | ] 36 | 37 | const content = '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。' 38 | const description = '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。' 39 | const href = 'https://ant.design' 40 | 41 | const article = (options) => { 42 | const queryParameters = getQueryParameters(options) 43 | console.log('queryParameters', queryParameters) 44 | if (queryParameters && !queryParameters.count) { 45 | queryParameters.count = 5 46 | } 47 | const data = [] 48 | for (let i = 0; i < queryParameters.count; i++) { 49 | const tmpKey = i + 1 50 | const num = parseInt(Math.random() * (4 + 1), 10) 51 | data.push({ 52 | id: tmpKey, 53 | avatar: avatar[num], 54 | owner: owner[num], 55 | content: content, 56 | star: Mock.mock('@integer(1, 999)'), 57 | percent: Mock.mock('@integer(1, 999)'), 58 | like: Mock.mock('@integer(1, 999)'), 59 | message: Mock.mock('@integer(1, 999)'), 60 | description: description, 61 | href: href, 62 | title: titles[ i % 8 ], 63 | updatedAt: Mock.mock('@datetime'), 64 | members: [ 65 | { 66 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', 67 | name: '曲丽丽', 68 | id: 'member1' 69 | }, 70 | { 71 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', 72 | name: '王昭君', 73 | id: 'member2' 74 | }, 75 | { 76 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', 77 | name: '董娜娜', 78 | id: 'member3' 79 | } 80 | ], 81 | activeUser: Math.ceil(Math.random() * 100000) + 100000, 82 | newUser: Math.ceil(Math.random() * 1000) + 1000, 83 | cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)] 84 | }) 85 | } 86 | return builder(data) 87 | } 88 | 89 | Mock.mock(/\/list\/article/, 'get', article) 90 | -------------------------------------------------------------------------------- /src/mock/services/auth.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs2' 2 | import { builder, getBody } from '../util' 3 | 4 | const username = ['admin', 'super'] 5 | // 强硬要求 ant.design 相同密码 6 | // '21232f297a57a5a743894a0e4a801fc3', 7 | const password = ['8914de686ab28dc22f30d3d8e107ff6c', '21232f297a57a5a743894a0e4a801fc3'] // admin, ant.design 8 | 9 | const login = (options) => { 10 | const body = getBody(options) 11 | console.log('mock: body', body) 12 | if (!username.includes(body.username) || !password.includes(body.password)) { 13 | return builder({ isLogin: true }, '账户或密码错误', 401) 14 | } 15 | 16 | return builder({ 17 | 'id': Mock.mock('@guid'), 18 | 'name': Mock.mock('@name'), 19 | 'username': 'admin', 20 | 'password': '', 21 | 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png', 22 | 'status': 1, 23 | 'telephone': '', 24 | 'lastLoginIp': '27.154.74.117', 25 | 'lastLoginTime': 1534837621348, 26 | 'creatorId': 'admin', 27 | 'createTime': 1497160610259, 28 | 'deleted': 0, 29 | 'roleId': 'admin', 30 | 'lang': 'zh-CN', 31 | 'token': '4291d7da9005377ec9aec4a71ea837f' 32 | }, '', 200, { 'Custom-Header': Mock.mock('@guid') }) 33 | } 34 | 35 | const logout = () => { 36 | return builder({}, '[测试接口] 注销成功') 37 | } 38 | 39 | const smsCaptcha = () => { 40 | return builder({ captcha: Mock.mock('@integer(10000, 99999)') }) 41 | } 42 | 43 | const twofactor = () => { 44 | return builder({ stepCode: Mock.mock('@integer(0, 1)') }) 45 | } 46 | 47 | Mock.mock(/\/auth\/login/, 'post', login) 48 | Mock.mock(/\/auth\/logout/, 'post', logout) 49 | Mock.mock(/\/account\/sms/, 'post', smsCaptcha) 50 | Mock.mock(/\/auth\/2step-code/, 'post', twofactor) 51 | -------------------------------------------------------------------------------- /src/mock/util.js: -------------------------------------------------------------------------------- 1 | const responseBody = { 2 | message: '', 3 | timestamp: 0, 4 | result: null, 5 | code: 0 6 | } 7 | 8 | export const builder = (data, message, code = 0, headers = {}) => { 9 | responseBody.result = data 10 | if (message !== undefined && message !== null) { 11 | responseBody.message = message 12 | } 13 | if (code !== undefined && code !== 0) { 14 | responseBody.code = code 15 | responseBody._status = code 16 | } 17 | if (headers !== null && typeof headers === 'object' && Object.keys(headers).length > 0) { 18 | responseBody._headers = headers 19 | } 20 | responseBody.timestamp = new Date().getTime() 21 | return responseBody 22 | } 23 | 24 | export const getQueryParameters = (options) => { 25 | const url = options.url 26 | const search = url.split('?')[1] 27 | if (!search) { 28 | return {} 29 | } 30 | return JSON.parse('{"' + decodeURIComponent(search) 31 | .replace(/"/g, '\\"') 32 | .replace(/&/g, '","') 33 | .replace(/=/g, '":"') + '"}') 34 | } 35 | 36 | export const getBody = (options) => { 37 | return options.body && JSON.parse(options.body) 38 | } 39 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import { constantRouterMap } from '@/config/router.config' 4 | 5 | // hack router push callback 6 | const originalPush = Router.prototype.push 7 | Router.prototype.push = function push (location, onResolve, onReject) { 8 | if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject) 9 | return originalPush.call(this, location).catch(err => err) 10 | } 11 | 12 | Vue.use(Router) 13 | 14 | const createRouter = () => 15 | new Router({ 16 | mode: 'history', 17 | routes: constantRouterMap 18 | }) 19 | 20 | const router = createRouter() 21 | 22 | // 定义一个resetRouter 方法,在退出登录后或token过期后 需要重新登录时,调用即可 23 | export function resetRouter () { 24 | const newRouter = createRouter() 25 | router.matcher = newRouter.matcher 26 | } 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /src/store/app-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const baseMixin = { 4 | computed: { 5 | ...mapState({ 6 | layout: state => state.app.layout, 7 | navTheme: state => state.app.theme, 8 | primaryColor: state => state.app.color, 9 | colorWeak: state => state.app.weak, 10 | fixedHeader: state => state.app.fixedHeader, 11 | fixedSidebar: state => state.app.fixedSidebar, 12 | contentWidth: state => state.app.contentWidth, 13 | autoHideHeader: state => state.app.autoHideHeader, 14 | 15 | isMobile: state => state.app.isMobile, 16 | sideCollapsed: state => state.app.sideCollapsed, 17 | multiTab: state => state.app.multiTab 18 | }), 19 | isTopMenu () { 20 | return this.layout === 'topmenu' 21 | } 22 | }, 23 | methods: { 24 | isSideMenu () { 25 | return !this.isTopMenu 26 | } 27 | } 28 | } 29 | 30 | export { 31 | baseMixin 32 | } 33 | -------------------------------------------------------------------------------- /src/store/device-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const deviceMixin = { 4 | computed: { 5 | ...mapState({ 6 | isMobile: state => state.app.isMobile 7 | }) 8 | } 9 | } 10 | 11 | export { deviceMixin } 12 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | isMobile: state => state.app.isMobile, 3 | lang: state => state.app.lang, 4 | theme: state => state.app.theme, 5 | color: state => state.app.color, 6 | token: state => state.user.token, 7 | avatar: state => state.user.avatar, 8 | nickname: state => state.user.name, 9 | welcome: state => state.user.welcome, 10 | roles: state => state.user.roles, 11 | userInfo: state => state.user.info, 12 | addRouters: state => state.permission.addRouters, 13 | multiTab: state => state.app.multiTab 14 | } 15 | 16 | export default getters 17 | -------------------------------------------------------------------------------- /src/store/i18n-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const i18nMixin = { 4 | computed: { 5 | ...mapState({ 6 | currentLang: state => state.app.lang 7 | }) 8 | }, 9 | methods: { 10 | setLang (lang) { 11 | this.$store.dispatch('setLang', lang) 12 | } 13 | } 14 | } 15 | 16 | export default i18nMixin 17 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import app from './modules/app' 5 | import user from './modules/user' 6 | 7 | // default router permission control 8 | // 默认路由模式为静态路由 (router.config.js) 9 | import permission from './modules/static-router' 10 | 11 | // dynamic router permission control (Experimental) 12 | // 动态路由模式(api请求后端生成) 13 | // import permission from './modules/async-router' 14 | 15 | import getters from './getters' 16 | 17 | Vue.use(Vuex) 18 | 19 | export default new Vuex.Store({ 20 | modules: { 21 | app, 22 | user, 23 | permission 24 | }, 25 | state: {}, 26 | mutations: {}, 27 | actions: {}, 28 | getters 29 | }) 30 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import storage from 'store' 2 | import { 3 | SIDEBAR_TYPE, 4 | TOGGLE_MOBILE_TYPE, 5 | TOGGLE_NAV_THEME, 6 | TOGGLE_LAYOUT, 7 | TOGGLE_FIXED_HEADER, 8 | TOGGLE_FIXED_SIDEBAR, 9 | TOGGLE_CONTENT_WIDTH, 10 | TOGGLE_HIDE_HEADER, 11 | TOGGLE_COLOR, 12 | TOGGLE_WEAK, 13 | TOGGLE_MULTI_TAB, 14 | // i18n 15 | APP_LANGUAGE 16 | } from '@/store/mutation-types' 17 | import { loadLanguageAsync } from '@/locales' 18 | 19 | const app = { 20 | state: { 21 | sideCollapsed: false, 22 | isMobile: false, 23 | theme: 'dark', 24 | layout: '', 25 | contentWidth: '', 26 | fixedHeader: false, 27 | fixedSidebar: false, 28 | autoHideHeader: false, 29 | color: '', 30 | weak: false, 31 | multiTab: true, 32 | lang: 'en-US', 33 | _antLocale: {} 34 | }, 35 | mutations: { 36 | [SIDEBAR_TYPE]: (state, type) => { 37 | state.sideCollapsed = type 38 | storage.set(SIDEBAR_TYPE, type) 39 | }, 40 | [TOGGLE_MOBILE_TYPE]: (state, isMobile) => { 41 | state.isMobile = isMobile 42 | }, 43 | [TOGGLE_NAV_THEME]: (state, theme) => { 44 | state.theme = theme 45 | storage.set(TOGGLE_NAV_THEME, theme) 46 | }, 47 | [TOGGLE_LAYOUT]: (state, mode) => { 48 | state.layout = mode 49 | storage.set(TOGGLE_LAYOUT, mode) 50 | }, 51 | [TOGGLE_FIXED_HEADER]: (state, mode) => { 52 | state.fixedHeader = mode 53 | storage.set(TOGGLE_FIXED_HEADER, mode) 54 | }, 55 | [TOGGLE_FIXED_SIDEBAR]: (state, mode) => { 56 | state.fixedSidebar = mode 57 | storage.set(TOGGLE_FIXED_SIDEBAR, mode) 58 | }, 59 | [TOGGLE_CONTENT_WIDTH]: (state, type) => { 60 | state.contentWidth = type 61 | storage.set(TOGGLE_CONTENT_WIDTH, type) 62 | }, 63 | [TOGGLE_HIDE_HEADER]: (state, type) => { 64 | state.autoHideHeader = type 65 | storage.set(TOGGLE_HIDE_HEADER, type) 66 | }, 67 | [TOGGLE_COLOR]: (state, color) => { 68 | state.color = color 69 | storage.set(TOGGLE_COLOR, color) 70 | }, 71 | [TOGGLE_WEAK]: (state, mode) => { 72 | state.weak = mode 73 | storage.set(TOGGLE_WEAK, mode) 74 | }, 75 | [APP_LANGUAGE]: (state, lang, antd = {}) => { 76 | state.lang = lang 77 | state._antLocale = antd 78 | storage.set(APP_LANGUAGE, lang) 79 | }, 80 | [TOGGLE_MULTI_TAB]: (state, bool) => { 81 | storage.set(TOGGLE_MULTI_TAB, bool) 82 | state.multiTab = bool 83 | } 84 | }, 85 | actions: { 86 | setLang ({ commit }, lang) { 87 | return new Promise((resolve, reject) => { 88 | commit(APP_LANGUAGE, lang) 89 | loadLanguageAsync(lang).then(() => { 90 | resolve() 91 | }).catch((e) => { 92 | reject(e) 93 | }) 94 | }) 95 | } 96 | } 97 | } 98 | 99 | export default app 100 | -------------------------------------------------------------------------------- /src/store/modules/async-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 向后端请求用户的菜单,动态生成路由 3 | */ 4 | import { constantRouterMap } from '@/config/router.config' 5 | import { generatorDynamicRouter } from '@/router/generator-routers' 6 | 7 | const permission = { 8 | state: { 9 | routers: constantRouterMap, 10 | addRouters: [] 11 | }, 12 | mutations: { 13 | SET_ROUTERS: (state, routers) => { 14 | state.addRouters = routers 15 | state.routers = constantRouterMap.concat(routers) 16 | } 17 | }, 18 | actions: { 19 | GenerateRoutes ({ commit }, data) { 20 | return new Promise((resolve, reject) => { 21 | const { token } = data 22 | generatorDynamicRouter(token).then(routers => { 23 | commit('SET_ROUTERS', routers) 24 | resolve() 25 | }).catch(e => { 26 | reject(e) 27 | }) 28 | }) 29 | } 30 | } 31 | } 32 | 33 | export default permission 34 | -------------------------------------------------------------------------------- /src/store/modules/static-router.js: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '@/config/router.config' 2 | import cloneDeep from 'lodash.clonedeep' 3 | 4 | /** 5 | * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除 6 | * 7 | * @param permission 8 | * @param route 9 | * @returns {boolean} 10 | */ 11 | function hasPermission (permission, route) { 12 | if (route.meta && route.meta.permission) { 13 | console.log('hasPermission', permission) 14 | if (permission === undefined) { 15 | return false 16 | } 17 | let flag = false 18 | for (let i = 0, len = permission.length; i < len; i++) { 19 | flag = route.meta.permission.includes(permission[i]) 20 | if (flag) { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | return true 27 | } 28 | 29 | /** 30 | * 单账户多角色时,使用该方法可过滤角色不存在的菜单 31 | * 32 | * @param roles 33 | * @param route 34 | * @returns {*} 35 | */ 36 | // eslint-disable-next-line 37 | function hasRole(roles, route) { 38 | if (route.meta && route.meta.roles) { 39 | return route.meta.roles.includes(roles.id) 40 | } else { 41 | return true 42 | } 43 | } 44 | 45 | function filterAsyncRouter (routerMap, role) { 46 | const accessedRouters = routerMap.filter(route => { 47 | if (hasPermission(role.permissionList, route)) { 48 | if (route.children && route.children.length) { 49 | route.children = filterAsyncRouter(route.children, role) 50 | } 51 | return true 52 | } 53 | return false 54 | }) 55 | return accessedRouters 56 | } 57 | 58 | const permission = { 59 | state: { 60 | routers: constantRouterMap, 61 | addRouters: [] 62 | }, 63 | mutations: { 64 | SET_ROUTERS: (state, routers) => { 65 | state.addRouters = routers 66 | state.routers = constantRouterMap.concat(routers) 67 | } 68 | }, 69 | actions: { 70 | GenerateRoutes ({ commit }, data) { 71 | return new Promise(resolve => { 72 | const { role } = data 73 | const routerMap = cloneDeep(asyncRouterMap) 74 | const accessedRouters = filterAsyncRouter(routerMap, role) 75 | commit('SET_ROUTERS', accessedRouters) 76 | resolve() 77 | }) 78 | } 79 | } 80 | } 81 | 82 | export default permission 83 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'Access-Token' 2 | 3 | export const SIDEBAR_TYPE = 'sidebar_type' 4 | export const TOGGLE_MOBILE_TYPE = 'is_mobile' 5 | export const TOGGLE_NAV_THEME = 'nav_theme' 6 | export const TOGGLE_LAYOUT = 'layout' 7 | export const TOGGLE_FIXED_HEADER = 'fixed_header' 8 | export const TOGGLE_FIXED_SIDEBAR = 'fixed_sidebar' 9 | export const TOGGLE_CONTENT_WIDTH = 'content_width' 10 | export const TOGGLE_HIDE_HEADER = 'auto_hide_header' 11 | export const TOGGLE_COLOR = 'color' 12 | export const TOGGLE_WEAK = 'weak' 13 | export const TOGGLE_MULTI_TAB = 'multi_tab' 14 | export const APP_LANGUAGE = 'app_language' 15 | 16 | export const CONTENT_WIDTH_TYPE = { 17 | Fluid: 'Fluid', 18 | Fixed: 'Fixed' 19 | } 20 | 21 | export const NAV_THEME = { 22 | LIGHT: 'light', 23 | DARK: 'dark' 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/axios.js: -------------------------------------------------------------------------------- 1 | const VueAxios = { 2 | vm: {}, 3 | // eslint-disable-next-line no-unused-vars 4 | install (Vue, instance) { 5 | if (this.installed) { 6 | return 7 | } 8 | this.installed = true 9 | 10 | if (!instance) { 11 | // eslint-disable-next-line no-console 12 | console.error('You have to install axios') 13 | return 14 | } 15 | 16 | Vue.axios = instance 17 | 18 | Object.defineProperties(Vue.prototype, { 19 | axios: { 20 | get: function get () { 21 | return instance 22 | } 23 | }, 24 | $http: { 25 | get: function get () { 26 | return instance 27 | } 28 | } 29 | }) 30 | } 31 | } 32 | 33 | export { 34 | VueAxios 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/domUtil.js: -------------------------------------------------------------------------------- 1 | import config from '@/config/defaultSettings' 2 | 3 | export const setDocumentTitle = function (title) { 4 | document.title = title 5 | const ua = navigator.userAgent 6 | // eslint-disable-next-line 7 | const regex = /\bMicroMessenger\/([\d\.]+)/ 8 | if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) { 9 | const i = document.createElement('iframe') 10 | i.src = '/favicon.ico' 11 | i.style.display = 'none' 12 | i.onload = function () { 13 | setTimeout(function () { 14 | i.remove() 15 | }, 9) 16 | } 17 | document.body.appendChild(i) 18 | } 19 | } 20 | 21 | export const domTitle = config.title 22 | -------------------------------------------------------------------------------- /src/utils/filter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import moment from 'moment' 3 | import 'moment/locale/zh-cn' 4 | moment.locale('zh-cn') 5 | 6 | Vue.filter('NumberFormat', function (value) { 7 | if (!value) { 8 | return '0' 9 | } 10 | const intPartFormat = value.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') // 将整数部分逢三一断 11 | return intPartFormat 12 | }) 13 | 14 | Vue.filter('dayjs', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 15 | return moment(dataStr).format(pattern) 16 | }) 17 | 18 | Vue.filter('moment', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 19 | return moment(dataStr).format(pattern) 20 | }) 21 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '@/store' 3 | import storage from 'store' 4 | import notification from 'ant-design-vue/es/notification' 5 | import { VueAxios } from './axios' 6 | import { ACCESS_TOKEN } from '@/store/mutation-types' 7 | 8 | // 创建 axios 实例 9 | const request = axios.create({ 10 | // API 请求的默认前缀 11 | baseURL: process.env.VUE_APP_API_BASE_URL, 12 | timeout: 6000 // 请求超时时间 13 | }) 14 | 15 | // 异常拦截处理器 16 | const errorHandler = (error) => { 17 | if (error.response) { 18 | const data = error.response.data 19 | // 从 localstorage 获取 token 20 | const token = storage.get(ACCESS_TOKEN) 21 | if (error.response.status === 403) { 22 | notification.error({ 23 | message: 'Forbidden', 24 | description: data.message 25 | }) 26 | } 27 | if (error.response.status === 401 && !(data.result && data.result.isLogin)) { 28 | notification.error({ 29 | message: 'Unauthorized', 30 | description: 'Authorization verification failed' 31 | }) 32 | if (token) { 33 | store.dispatch('Logout').then(() => { 34 | setTimeout(() => { 35 | window.location.reload() 36 | }, 1500) 37 | }) 38 | } 39 | } 40 | } 41 | return Promise.reject(error) 42 | } 43 | 44 | // request interceptor 45 | request.interceptors.request.use(config => { 46 | const token = storage.get(ACCESS_TOKEN) 47 | // 如果 token 存在 48 | // 让每个请求携带自定义 token 请根据实际情况自行修改 49 | if (token) { 50 | config.headers[ACCESS_TOKEN] = token 51 | } 52 | return config 53 | }, errorHandler) 54 | 55 | // response interceptor 56 | request.interceptors.response.use((response) => { 57 | return response.data 58 | }, errorHandler) 59 | 60 | const installer = { 61 | vm: {}, 62 | install (Vue) { 63 | Vue.use(VueAxios, request) 64 | } 65 | } 66 | 67 | export default request 68 | 69 | export { 70 | installer as VueAxios, 71 | request as axios 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/routeConvert.js: -------------------------------------------------------------------------------- 1 | import cloneDeep from 'lodash.clonedeep' 2 | 3 | export function convertRoutes (nodes) { 4 | if (!nodes) return null 5 | 6 | nodes = cloneDeep(nodes) 7 | 8 | let queue = Array.isArray(nodes) ? nodes.concat() : [nodes] 9 | 10 | while (queue.length) { 11 | const levelSize = queue.length 12 | 13 | for (let i = 0; i < levelSize; i++) { 14 | const node = queue.shift() 15 | 16 | if (!node.children || !node.children.length) continue 17 | 18 | node.children.forEach(child => { 19 | // 转化相对路径 20 | if (child.path[0] !== '/' && !child.path.startsWith('http')) { 21 | child.path = node.path.replace(/(\w*)[/]*$/, `$1/${child.path}`) 22 | } 23 | }) 24 | 25 | queue = queue.concat(node.children) 26 | } 27 | } 28 | 29 | return nodes 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/screenLog.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export const printANSI = () => { 3 | // console.clear() 4 | console.log('[antd pro] created()') 5 | // ASCII - ANSI Shadow 6 | let text = ` 7 | █████╗ ███╗ ██╗████████╗██████╗ ██████╗ ██████╗ ██████╗ 8 | ██╔══██╗████╗ ██║╚══██╔══╝██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗ 9 | ███████║██╔██╗ ██║ ██║ ██║ ██║ ██████╔╝██████╔╝██║ ██║ 10 | ██╔══██║██║╚██╗██║ ██║ ██║ ██║ ██╔═══╝ ██╔══██╗██║ ██║ 11 | ██║ ██║██║ ╚████║ ██║ ██████╔╝ ██║ ██║ ██║╚██████╔╝ 12 | ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 13 | \t\t\t\t\tPublished ${APP_VERSION}-${GIT_HASH} @ antdv.com 14 | \t\t\t\t\tBuild date: ${BUILD_DATE}` 15 | console.log(`%c${text}`, 'color: #fc4d50') 16 | console.log('%c感谢使用 antd pro!', 'color: #000; font-size: 14px; font-family: Hiragino Sans GB,Microsoft YaHei,\\\\5FAE\\8F6F\\96C5\\9ED1,Droid Sans Fallback,Source Sans,Wenquanyi Micro Hei,WenQuanYi Micro Hei Mono,WenQuanYi Zen Hei,Apple LiGothic Medium,SimHei,ST Heiti,WenQuanYi Zen Hei Sharp,sans-serif;') 17 | console.log('%cThanks for using antd pro!', 'color: #fff; font-size: 14px; font-weight: 300; text-shadow:#000 1px 0 0,#000 0 1px 0,#000 -1px 0 0,#000 0 -1px 0;') 18 | console.log('') 19 | console.log('%c默认使用的路由初始化模式可能是 静态路由 / 动态路由, 请前往 src/store/index.js 确认 import permission from 哪一个文件.', 'color: #000; font-size: 14px; font-family: Hiragino Sans GB,Microsoft YaHei,\\\\5FAE\\8F6F\\96C5\\9ED1,Droid Sans Fallback,Source Sans,Wenquanyi Micro Hei,WenQuanYi Micro Hei Mono,WenQuanYi Zen Hei,Apple LiGothic Medium,SimHei,ST Heiti,WenQuanYi Zen Hei Sharp,sans-serif;') 20 | console.log('') 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/util.js: -------------------------------------------------------------------------------- 1 | export function timeFix () { 2 | const time = new Date() 3 | const hour = time.getHours() 4 | return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好' 5 | } 6 | 7 | export function welcome () { 8 | const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 DOTA', '我猜你可能累了'] 9 | const index = Math.floor(Math.random() * arr.length) 10 | return arr[index] 11 | } 12 | 13 | /** 14 | * 触发 window.resize 15 | */ 16 | export function triggerWindowResizeEvent () { 17 | const event = document.createEvent('HTMLEvents') 18 | event.initEvent('resize', true, true) 19 | event.eventType = 'message' 20 | window.dispatchEvent(event) 21 | } 22 | 23 | export function handleScrollHeader (callback) { 24 | let timer = 0 25 | 26 | let beforeScrollTop = window.pageYOffset 27 | callback = callback || function () {} 28 | window.addEventListener( 29 | 'scroll', 30 | event => { 31 | clearTimeout(timer) 32 | timer = setTimeout(() => { 33 | let direction = 'up' 34 | const afterScrollTop = window.pageYOffset 35 | const delta = afterScrollTop - beforeScrollTop 36 | if (delta === 0) { 37 | return false 38 | } 39 | direction = delta > 0 ? 'down' : 'up' 40 | callback(direction) 41 | beforeScrollTop = afterScrollTop 42 | }, 50) 43 | }, 44 | false 45 | ) 46 | } 47 | 48 | export function isIE () { 49 | const bw = window.navigator.userAgent 50 | const compare = (s) => bw.indexOf(s) >= 0 51 | const ie11 = (() => 'ActiveXObject' in window)() 52 | return compare('MSIE') || ie11 53 | } 54 | 55 | /** 56 | * Remove loading animate 57 | * @param id parent element id or class 58 | * @param timeout 59 | */ 60 | export function removeLoadingAnimate (id = '', timeout = 1500) { 61 | if (id === '') { 62 | return 63 | } 64 | setTimeout(() => { 65 | document.body.removeChild(document.getElementById(id)) 66 | }, timeout) 67 | } 68 | export function scorePassword (pass) { 69 | let score = 0 70 | if (!pass) { 71 | return score 72 | } 73 | // award every unique letter until 5 repetitions 74 | const letters = {} 75 | for (let i = 0; i < pass.length; i++) { 76 | letters[pass[i]] = (letters[pass[i]] || 0) + 1 77 | score += 5.0 / letters[pass[i]] 78 | } 79 | 80 | // bonus points for mixing it up 81 | const variations = { 82 | digits: /\d/.test(pass), 83 | lower: /[a-z]/.test(pass), 84 | upper: /[A-Z]/.test(pass), 85 | nonWords: /\W/.test(pass) 86 | } 87 | 88 | let variationCount = 0 89 | for (var check in variations) { 90 | variationCount += (variations[check] === true) ? 1 : 0 91 | } 92 | score += (variationCount - 1) * 10 93 | 94 | return parseInt(score) 95 | } 96 | -------------------------------------------------------------------------------- /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 | position: relative; 10 | max-height: @line * 1.5em; 11 | padding-right: 1em; 12 | margin-right: -1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | 17 | &::before { 18 | position: absolute; 19 | right: 14px; 20 | bottom: 0; 21 | padding: 0 1px; 22 | background: @bg; 23 | content: '...'; 24 | } 25 | 26 | &::after { 27 | position: absolute; 28 | right: 14px; 29 | width: 1em; 30 | height: 1em; 31 | margin-top: .2em; 32 | background: white; 33 | content: ''; 34 | } 35 | } 36 | 37 | // mixins for clearfix 38 | // ------------------------ 39 | .clearfix() { 40 | zoom: 1; 41 | 42 | &::before, 43 | &::after { 44 | display: table; 45 | content: ' '; 46 | } 47 | 48 | &::after { 49 | height: 0; 50 | clear: both; 51 | font-size: 0; 52 | visibility: hidden; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/account/center/page/Article.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /src/views/account/center/page/index.js: -------------------------------------------------------------------------------- 1 | import AppPage from './App' 2 | import ArticlePage from './Article' 3 | import ProjectPage from './Project' 4 | 5 | export { AppPage, ArticlePage, ProjectPage } 6 | -------------------------------------------------------------------------------- /src/views/account/settings/Binding.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /src/views/account/settings/Custom.vue: -------------------------------------------------------------------------------- 1 | 32 | 69 | -------------------------------------------------------------------------------- /src/views/account/settings/Notification.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /src/views/account/settings/Security.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /src/views/dashboard/Monitor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/dashboard/Workplace.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default.less'; 2 | 3 | .text-overflow() { 4 | overflow: hidden; 5 | text-overflow: ellipsis; 6 | word-break: break-all; 7 | white-space: nowrap; 8 | } 9 | 10 | // mixins for clearfix 11 | // ------------------------ 12 | .clearfix() { 13 | zoom: 1; 14 | 15 | &::before, 16 | &::after { 17 | display: table; 18 | content: ' '; 19 | } 20 | 21 | &::after { 22 | height: 0; 23 | clear: both; 24 | font-size: 0; 25 | visibility: hidden; 26 | } 27 | } 28 | 29 | .page-header-content { 30 | display: flex; 31 | 32 | .avatar { 33 | flex: 0 1 72px; 34 | 35 | & > span { 36 | display: block; 37 | width: 72px; 38 | height: 72px; 39 | border-radius: 72px; 40 | } 41 | } 42 | 43 | .content { 44 | position: relative; 45 | top: 4px; 46 | margin-left: 24px; 47 | line-height: 22px; 48 | color: @text-color-secondary; 49 | flex: 1 1 auto; 50 | 51 | .content-title { 52 | margin-bottom: 12px; 53 | font-size: 20px; 54 | font-weight: 500; 55 | line-height: 28px; 56 | color: @heading-color; 57 | } 58 | } 59 | } 60 | 61 | .extra-content { 62 | .clearfix(); 63 | 64 | float: right; 65 | white-space: nowrap; 66 | 67 | .stat-item { 68 | position: relative; 69 | display: inline-block; 70 | padding: 0 32px; 71 | 72 | > p:first-child { 73 | margin-bottom: 4px; 74 | font-size: @font-size-base; 75 | line-height: 22px; 76 | color: @text-color-secondary; 77 | } 78 | 79 | > p { 80 | margin: 0; 81 | font-size: 30px; 82 | line-height: 38px; 83 | color: @heading-color; 84 | 85 | > span { 86 | font-size: 20px; 87 | color: @text-color-secondary; 88 | } 89 | } 90 | 91 | &::after { 92 | position: absolute; 93 | top: 8px; 94 | right: 0; 95 | width: 1px; 96 | height: 40px; 97 | background-color: @border-color-split; 98 | content: ''; 99 | } 100 | 101 | &:last-child { 102 | padding-right: 0; 103 | 104 | &::after { 105 | display: none; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /src/views/form/stepForm/Step3.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 50 | 65 | -------------------------------------------------------------------------------- /src/views/form/stepForm/StepForm.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 60 | 61 | 67 | -------------------------------------------------------------------------------- /src/views/list/QueryList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 51 | -------------------------------------------------------------------------------- /src/views/list/components/Info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 58 | -------------------------------------------------------------------------------- /src/views/list/modules/CreateForm.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 73 | -------------------------------------------------------------------------------- /src/views/list/modules/TaskForm.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 95 | -------------------------------------------------------------------------------- /src/views/list/search/SearchLayout.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 73 | 74 | 80 | -------------------------------------------------------------------------------- /src/views/list/search/components/CardInfo.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 59 | -------------------------------------------------------------------------------- /src/views/list/search/components/IconText.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | -------------------------------------------------------------------------------- /src/views/other/IconSelectorView.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | -------------------------------------------------------------------------------- /src/views/other/modules/OrgModal.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 101 | -------------------------------------------------------------------------------- /src/views/result/Error.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 40 | -------------------------------------------------------------------------------- /src/views/user/RegisterResult.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | --------------------------------------------------------------------------------