├── .browserslistrc ├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── README.md ├── babel.config.js ├── build.bat ├── cypress.json ├── jest.config.js ├── nswag ├── refresh.bat ├── service.config.nswag └── service.extensions.ts ├── package.json ├── postcss.config.js ├── public ├── app.json ├── assets │ ├── appconfig.dev.json │ ├── appconfig.prod.json │ ├── locales │ │ ├── en_US.json │ │ └── zh_CN.json │ └── logo-color.svg ├── avatar2.jpg ├── color.less ├── favicon.ico ├── img │ └── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── msapplication-icon-144x144.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg ├── index.html ├── loading │ ├── loading.css │ ├── loading.html │ └── option2 │ │ ├── html_code_segment.html │ │ ├── loading.css │ │ └── loading.svg ├── logo.png ├── manifest.json └── robots.txt ├── src ├── App.vue ├── _mock │ ├── _api.ts │ ├── _chart.ts │ ├── _fake.ts │ ├── _passport.ts │ ├── _profile.ts │ ├── _rule.ts │ └── index.ts ├── api │ ├── index.ts │ ├── login.ts │ └── manage.ts ├── assets │ ├── app.json │ ├── background.svg │ ├── icons │ │ └── bx-analyse.svg │ ├── logo-full.svg │ ├── logo.png │ ├── logo.svg │ ├── theme │ │ └── styles │ │ │ ├── _antd.less │ │ │ ├── app │ │ │ ├── _freak.less │ │ │ ├── _functions.less │ │ │ ├── _preloader.less │ │ │ ├── antd │ │ │ │ ├── _preserve-white-spaces.less │ │ │ │ ├── button.less │ │ │ │ ├── card.less │ │ │ │ ├── carousel.less │ │ │ │ ├── form.less │ │ │ │ ├── index.less │ │ │ │ ├── input.less │ │ │ │ ├── modal.less │ │ │ │ └── table.less │ │ │ ├── aside.less │ │ │ ├── delon │ │ │ │ ├── acl.less │ │ │ │ ├── form.less │ │ │ │ ├── index.less │ │ │ │ └── search-form-pro.less │ │ │ ├── fullscreen.less │ │ │ ├── header.less │ │ │ ├── index.less │ │ │ ├── layout.less │ │ │ ├── login.less │ │ │ ├── mixins │ │ │ │ ├── index.less │ │ │ │ └── text-truncate.less │ │ │ ├── router.less │ │ │ ├── scrollbar.less │ │ │ ├── type.less │ │ │ ├── utils │ │ │ │ ├── abs.less │ │ │ │ ├── align.less │ │ │ │ ├── border.less │ │ │ │ ├── code.less │ │ │ │ ├── color.less │ │ │ │ ├── display.less │ │ │ │ ├── float.less │ │ │ │ ├── icon.less │ │ │ │ ├── img.less │ │ │ │ ├── index.less │ │ │ │ ├── other.less │ │ │ │ ├── position.less │ │ │ │ ├── responsive.less │ │ │ │ ├── rotate.less │ │ │ │ ├── spacing.less │ │ │ │ ├── text.less │ │ │ │ └── width.less │ │ │ └── widgets │ │ │ │ ├── badge.less │ │ │ │ ├── form.less │ │ │ │ ├── half-float.less │ │ │ │ ├── index.less │ │ │ │ ├── masonry-grid.less │ │ │ │ ├── no-data.less │ │ │ │ ├── placeholder.less │ │ │ │ ├── router-progress-bar.less │ │ │ │ ├── table.less │ │ │ │ └── user-block.less │ │ │ ├── default.less │ │ │ ├── index.less │ │ │ ├── layout │ │ │ ├── default │ │ │ │ ├── app-icons.less │ │ │ │ ├── aside.less │ │ │ │ ├── fix │ │ │ │ │ ├── footer-toolbar.less │ │ │ │ │ ├── full-content.less │ │ │ │ │ ├── index.less │ │ │ │ │ ├── page-header.less │ │ │ │ │ ├── quick-menu.less │ │ │ │ │ ├── reuse-tab.less │ │ │ │ │ └── sidebar-nav.less │ │ │ │ ├── fixed.less │ │ │ │ ├── header.less │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.less │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── layout.less │ │ │ │ ├── progress-bar.less │ │ │ │ ├── user.less │ │ │ │ └── variable.less │ │ │ └── fullscreen │ │ │ │ ├── fix │ │ │ │ ├── index.less │ │ │ │ └── page-header.less │ │ │ │ ├── index.en-US.md │ │ │ │ ├── index.less │ │ │ │ ├── index.zh-CN.md │ │ │ │ ├── layout.less │ │ │ │ └── variable.less │ │ │ └── variable │ │ │ ├── ad-badge.less │ │ │ ├── ad-masonry.less │ │ │ ├── ad-modal.less │ │ │ ├── ad-reuse-tab.less │ │ │ ├── alain-form.less │ │ │ ├── alain-layout.less │ │ │ ├── alain-ng.less │ │ │ ├── alain-placeholder.less │ │ │ ├── alain-table.less │ │ │ ├── color.less │ │ │ └── global.less │ └── tmp │ │ ├── app-data.json │ │ ├── demo.docx │ │ ├── demo.pdf │ │ ├── demo.pptx │ │ ├── demo.xlsx │ │ ├── i18n │ │ ├── en.json │ │ └── zh-CN.json │ │ └── img │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── avatar.jpg │ │ ├── bg1.jpg │ │ ├── bg10.jpg │ │ ├── bg2.jpg │ │ ├── bg3.jpg │ │ ├── bg4.jpg │ │ ├── bg5.jpg │ │ ├── bg6.jpg │ │ ├── bg7.jpg │ │ ├── bg8.jpg │ │ ├── bg9.jpg │ │ └── half-float-bg-1.jpg ├── components │ ├── _util │ │ └── util.ts │ ├── avatar-list │ │ ├── index.less │ │ ├── index.md │ │ ├── index.ts │ │ ├── item.vue │ │ └── list.vue │ ├── charts │ │ ├── bar.vue │ │ ├── chart-card.vue │ │ ├── chart.less │ │ ├── liquid.vue │ │ ├── mini-area.vue │ │ ├── mini-bar.vue │ │ ├── mini-progress.vue │ │ ├── mini-smooth-area.vue │ │ ├── radar.vue │ │ ├── rank-list.vue │ │ ├── smooth.area.less │ │ ├── tag-cloud.vue │ │ ├── transfer-bar.vue │ │ └── trend.vue │ ├── count-down │ │ ├── count-down.vue │ │ └── index.ts │ ├── detail-list │ │ ├── detail-list-item.vue │ │ ├── detail-list.vue │ │ └── index.ts │ ├── ellipsis │ │ ├── ellipsis.vue │ │ └── index.ts │ ├── exception │ │ ├── exception-page.vue │ │ ├── index.ts │ │ └── type.ts │ ├── footer-tool-bar │ │ ├── footer-tool-bar.vue │ │ ├── index.less │ │ └── index.ts │ ├── global-footer │ │ ├── global-footer.vue │ │ └── index.ts │ ├── global-header │ │ ├── global-header.vue │ │ └── index.ts │ ├── global.less │ ├── icon-selector │ │ ├── icon-selector.vue │ │ ├── icons.ts │ │ └── index.ts │ ├── index.less │ ├── index.ts │ ├── menu │ │ ├── index.ts │ │ ├── menu.render.tsx │ │ ├── menu.tsx │ │ └── side-menu.vue │ ├── multi-tab │ │ ├── index.less │ │ ├── index.ts │ │ └── multi-tab.vue │ ├── notice-icon │ │ ├── index.ts │ │ └── notice-icon.vue │ ├── number-info │ │ ├── index.less │ │ ├── index.ts │ │ └── number-info.vue │ ├── page-header │ │ ├── index.ts │ │ └── page-header.vue │ ├── page-loading │ │ ├── index.ts │ │ └── page-loading.tsx │ ├── result │ │ ├── index.ts │ │ └── result.vue │ ├── setting-drawer │ │ ├── index.ts │ │ ├── setting-config.ts │ │ ├── setting-drawer.vue │ │ └── setting-item.vue │ ├── tag-select │ │ ├── index.ts │ │ ├── tag-select-option.tsx │ │ └── tag-select.tsx │ ├── tools │ │ ├── breadcrumb.vue │ │ ├── head-info.vue │ │ ├── logo.vue │ │ └── user-menu.vue │ ├── tree │ │ ├── index.ts │ │ └── s-tree.tsx │ └── trend │ │ ├── index.less │ │ ├── index.ts │ │ └── trend.vue ├── config │ ├── default-settings.ts │ └── router.config.ts ├── core │ ├── bootstrap.ts │ ├── icons.ts │ ├── lazy_lib │ │ └── components_use.ts │ ├── lazy_use.ts │ └── use.ts ├── layouts │ ├── account │ │ ├── account-layout.component.ts │ │ ├── account-layout.less │ │ └── account-layout.vue │ ├── admin │ │ ├── admin-layout.component.ts │ │ ├── admin-layout.less │ │ └── admin-layout.vue │ ├── blank │ │ └── blank-layout.vue │ ├── commons │ │ └── route-view.vue │ └── index.ts ├── main.ts ├── mock │ ├── index.ts │ ├── services │ │ ├── auth.ts │ │ ├── manage.ts │ │ ├── other.ts │ │ ├── tagCloud.ts │ │ └── user.ts │ └── util.ts ├── permission.ts ├── registerServiceWorker.ts ├── router │ ├── README.md │ └── index.ts ├── shared │ └── mixins │ │ ├── app-device-enquire.ts │ │ ├── mixin-device.ts │ │ └── mixin.ts ├── shims-ant-design-vue-type.ts ├── shims-ant-design-vue.d.ts ├── shims-tsx.d.ts ├── shims-v-charts.d.ts ├── shims-vue-ls-type.ts ├── shims-vue-ls.d.ts ├── shims-vue.d.ts ├── store │ ├── getters.ts │ ├── index.ts │ ├── interface.ts │ ├── modules │ │ ├── app.ts │ │ ├── permission.ts │ │ └── user.ts │ └── mutation-types.ts ├── typings.d.ts ├── utils │ ├── axios.ts │ ├── device.ts │ ├── domUtil.ts │ ├── filter.ts │ ├── helper │ │ └── permission.ts │ ├── permissions.ts │ ├── request.ts │ ├── storage.ts │ └── util.ts └── views │ ├── 404.vue │ ├── Home.vue │ ├── account │ └── login │ │ ├── login.component.ts │ │ ├── login.less │ │ └── login.vue │ └── dashboard │ ├── demo.component.ts │ ├── demo.less │ ├── demo.vue │ ├── monitor.component.ts │ ├── monitor.less │ └── monitor.vue ├── tests ├── e2e │ ├── plugins │ │ └── index.js │ ├── specs │ │ └── test.js │ └── support │ │ ├── commands.js │ │ └── index.js └── unit │ └── example.spec.ts ├── tsconfig.json ├── tslint.json ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=crlf 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # V1.2.0 2 | 1. 新增 reusetab 组件 3 | 2. 使用 v-charts 重构图表组件,包括 miniarea 、minibar、timeline 等 4 | 3. 修复菜单收起显示不正常的问题 5 | 4. 完善多语言信息 6 | 5. 实现登录功能 7 | ... 8 | 9 | # V1.1.0 10 | 实现基本页面 11 | 12 | ... 13 | 14 | # V1.0.0 15 | 搭建vue-alain基本组件框架 16 | 17 | ... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ant-design-pro-vue-ts 2 | 3 | 参考 vue-alain ,将 ant-design-pro-vue 修改为 typescript 版本 -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | node .\node_modules\@vue\cli-service\bin\vue-cli-service.js build -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue', 7 | 'ts', 8 | 'tsx' 9 | ], 10 | transform: { 11 | '^.+\\.vue$': 'vue-jest', 12 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 13 | '^.+\\.tsx?$': 'ts-jest' 14 | }, 15 | moduleNameMapper: { 16 | '^@/(.*)$': '/src/$1' 17 | }, 18 | snapshotSerializers: [ 19 | 'jest-serializer-vue' 20 | ], 21 | testMatch: [ 22 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 23 | ], 24 | testURL: 'http://localhost/' 25 | } 26 | -------------------------------------------------------------------------------- /nswag/refresh.bat: -------------------------------------------------------------------------------- 1 | "..\node_modules\.bin\nswag" run -------------------------------------------------------------------------------- /nswag/service.config.nswag: -------------------------------------------------------------------------------- 1 | { 2 | "runtime": "Default", 3 | "defaultVariables": null, 4 | "documentGenerator": { 5 | "fromDocument": { 6 | "url": "http://localhost:22742/swagger/v1/swagger.json", 7 | "output": null 8 | } 9 | }, 10 | "codeGenerators": { 11 | "openApiToTypeScriptClient": { 12 | "className": "{controller}ServiceProxy", 13 | "moduleName": "", 14 | "namespace": "", 15 | "typeScriptVersion": 2.0, 16 | "template": "Axios", 17 | "promiseType": "Promise", 18 | "httpClass": "HttpClient", 19 | "useSingletonProvider": false, 20 | "injectionTokenType": "InjectionToken", 21 | "rxJsVersion": 6.0, 22 | "dateTimeType": "MomentJS", 23 | "nullValue": "Undefined", 24 | "generateClientClasses": true, 25 | "generateClientInterfaces": false, 26 | "generateOptionalParameters": false, 27 | "exportTypes": true, 28 | "wrapDtoExceptions": false, 29 | "exceptionClass": "SwaggerException", 30 | "clientBaseClass": null, 31 | "wrapResponses": false, 32 | "wrapResponseMethods": [], 33 | "generateResponseClasses": true, 34 | "responseClass": "SwaggerResponse", 35 | "protectedMethods": [], 36 | "configurationClass": null, 37 | "useTransformOptionsMethod": false, 38 | "useTransformResultMethod": false, 39 | "generateDtoTypes": true, 40 | "operationGenerationMode": "MultipleClientsFromPathSegments", 41 | "markOptionalProperties": false, 42 | "generateCloneMethod": true, 43 | "typeStyle": "Class", 44 | "classTypes": [], 45 | "extendedClasses": [], 46 | "extensionCode": "service.extensions.ts", 47 | "generateDefaultValues": true, 48 | "excludedTypeNames": [], 49 | "excludedParameterNames": [], 50 | "handleReferences": false, 51 | "generateConstructorInterface": true, 52 | "convertConstructorInterfaceData": false, 53 | "importRequiredTypes": true, 54 | "useGetBaseUrlMethod": false, 55 | "baseUrlTokenName": "API_BASE_URL", 56 | "queryNullValue": "", 57 | "inlineNamedDictionaries": false, 58 | "inlineNamedAny": false, 59 | "templateDirectory": null, 60 | "typeNameGeneratorType": null, 61 | "propertyNameGeneratorType": null, 62 | "enumNameGeneratorType": null, 63 | "serviceHost": null, 64 | "serviceSchemes": null, 65 | "output": "../src/shared/service-proxies/service-proxies.ts" 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /nswag/service.extensions.ts: -------------------------------------------------------------------------------- 1 | import 'rxjs/add/operator/finally'; -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "name": "vue-alain" 4 | } 5 | } -------------------------------------------------------------------------------- /public/assets/appconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "remoteServiceBaseUrl":"http://localhost:6297", 3 | "momentLocaleMappings": [ 4 | { 5 | "from": "en", 6 | "to": "en" 7 | }, 8 | { 9 | "from": "zh-Hans", 10 | "to": "zh-cn" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /public/assets/appconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "momentLocaleMappings": [ 3 | { 4 | "from": "en", 5 | "to": "en" 6 | }, 7 | { 8 | "from": "zh-Hans", 9 | "to": "zh-cn" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /public/assets/logo-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 15 | 21 | 25 | 26 | -------------------------------------------------------------------------------- /public/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/avatar2.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ant Design Pro 9 | 10 | 11 | 12 | 15 |
16 |
Loading
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/loading/loading.css: -------------------------------------------------------------------------------- 1 | #preloadingAnimation{position:fixed;left:0;top:0;height:100%;width:100%;background:#ffffff;user-select:none;z-index: 9999;overflow: hidden}.lds-roller{display:inline-block;position:relative;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(0.5,0,0.5,1) infinite;transform-origin:32px 32px;}.lds-roller div:after{content:" ";display:block;position:absolute;width:6px;height:6px;border-radius:50%;background:#13c2c2;margin:-3px 0 0 -3px;}.lds-roller div:nth-child(1){animation-delay:-0.036s;}.lds-roller div:nth-child(1):after{top:50px;left:50px;}.lds-roller div:nth-child(2){animation-delay:-0.072s;}.lds-roller div:nth-child(2):after{top:54px;left:45px;}.lds-roller div:nth-child(3){animation-delay:-0.108s;}.lds-roller div:nth-child(3):after{top:57px;left:39px;}.lds-roller div:nth-child(4){animation-delay:-0.144s;}.lds-roller div:nth-child(4):after{top:58px;left:32px;}.lds-roller div:nth-child(5){animation-delay:-0.18s;}.lds-roller div:nth-child(5):after{top:57px;left:25px;}.lds-roller div:nth-child(6){animation-delay:-0.216s;}.lds-roller div:nth-child(6):after{top:54px;left:19px;}.lds-roller div:nth-child(7){animation-delay:-0.252s;}.lds-roller div:nth-child(7):after{top:50px;left:14px;}.lds-roller div:nth-child(8){animation-delay:-0.288s;}.lds-roller div:nth-child(8):after{top:45px;left:10px;}#preloadingAnimation .load-tips{color: #13c2c2;font-size:2rem;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin-top:80px;text-align:center;width:400px;height:64px;} @keyframes lds-roller{0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);}} -------------------------------------------------------------------------------- /public/loading/loading.html: -------------------------------------------------------------------------------- 1 |
Loading
-------------------------------------------------------------------------------- /public/loading/option2/html_code_segment.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
-------------------------------------------------------------------------------- /public/loading/option2/loading.css: -------------------------------------------------------------------------------- 1 | .preloading-animate{background:#ffffff;width:100%;height:100%;position:fixed;left:0;top:0;z-index:299;}.preloading-animate .preloading-wrapper{position:absolute;width:5rem;height:5rem;left:50%;top:50%;transform:translate(-50%,-50%);}.preloading-animate .preloading-wrapper .preloading-balls{font-size:5rem;} -------------------------------------------------------------------------------- /public/loading/option2/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/public/logo.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-alain", 3 | "short_name": "vue-alain", 4 | "icons": [ 5 | { 6 | "src": "/img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 46 | 51 | -------------------------------------------------------------------------------- /src/_mock/_api.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs2'; 2 | 3 | function getTags() { 4 | return Mock.mock({ 5 | 'list|100': [{ 'name': '@city', 'value|1-100': 150, 'type|0-2': 1 }], 6 | }); 7 | } 8 | 9 | Mock.mock(RegExp('/api/tag'), 'get', getTags); 10 | -------------------------------------------------------------------------------- /src/_mock/_passport.ts: -------------------------------------------------------------------------------- 1 | 2 | import Mock from 'mockjs2'; 3 | 4 | Mock.mock('/login', 'post', (opt: any) => { 5 | const postData = JSON.parse(opt.body); 6 | if ( postData.username === 'admin' && postData.password === '123qwe') { 7 | return { 8 | token: 'admin', 9 | }; 10 | } 11 | 12 | return { 13 | error: '账户或密码错误', 14 | }; 15 | }); 16 | 17 | export default Mock; 18 | -------------------------------------------------------------------------------- /src/_mock/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import rulemock from './_rule'; 3 | import fake from './_fake'; 4 | import './_api'; 5 | import './_chart'; 6 | import './_profile'; 7 | import './_profile'; 8 | import './_passport'; 9 | export default { 10 | rulemock, 11 | fake, 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | const api = { 2 | Login: '/auth/login', 3 | Logout: '/auth/logout', 4 | ForgePassword: '/auth/forge-password', 5 | Register: '/auth/register', 6 | twoStepCode: '/auth/2step-code', 7 | SendSms: '/account/sms', 8 | SendSmsErr: '/account/sms_err', 9 | // get my info 10 | UserInfo: '/user/info' 11 | } 12 | export default api 13 | -------------------------------------------------------------------------------- /src/api/login.ts: -------------------------------------------------------------------------------- 1 | import api from '.' 2 | import { axios } from '@/utils/request' 3 | 4 | /** 5 | * login func 6 | * parameter: { 7 | * username: '', 8 | * password: '', 9 | * remember_me: true, 10 | * captcha: '12345' 11 | * } 12 | * @param parameter 13 | * @returns {*} 14 | */ 15 | export function login (parameter) { 16 | return axios({ 17 | url: '/auth/login', 18 | method: 'post', 19 | data: parameter 20 | }) 21 | } 22 | 23 | export function getSmsCaptcha (parameter) { 24 | return axios({ 25 | url: api.SendSms, 26 | method: 'post', 27 | data: parameter 28 | }) 29 | } 30 | 31 | export function getInfo () { 32 | return axios({ 33 | url: '/user/info', 34 | method: 'get', 35 | headers: { 36 | 'Content-Type': 'application/json;charset=UTF-8' 37 | } 38 | }) 39 | } 40 | 41 | export function logout () { 42 | return axios({ 43 | url: '/auth/logout', 44 | method: 'post', 45 | headers: { 46 | 'Content-Type': 'application/json;charset=UTF-8' 47 | } 48 | }) 49 | } 50 | 51 | /** 52 | * get user 2step code open? 53 | * @param parameter {*} 54 | */ 55 | export function get2step (parameter) { 56 | return axios({ 57 | url: api.twoStepCode, 58 | method: 'post', 59 | data: parameter 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /src/api/manage.ts: -------------------------------------------------------------------------------- 1 | import { axios } 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 axios({ 16 | url: api.user, 17 | method: 'get', 18 | params: parameter 19 | }) 20 | } 21 | 22 | export function getRoleList (parameter) { 23 | return axios({ 24 | url: api.role, 25 | method: 'get', 26 | params: parameter 27 | }) 28 | } 29 | 30 | export function getServiceList (parameter) { 31 | return axios({ 32 | url: api.service, 33 | method: 'get', 34 | params: parameter 35 | }) 36 | } 37 | 38 | export function getPermissions (parameter) { 39 | return axios({ 40 | url: api.permissionNoPager, 41 | method: 'get', 42 | params: parameter 43 | }) 44 | } 45 | 46 | export function getOrgTree (parameter) { 47 | return axios({ 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 axios({ 58 | url: api.service, 59 | method: parameter.id === 0 ? 'post' : 'put', 60 | data: parameter 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /src/assets/app.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/app.json -------------------------------------------------------------------------------- /src/assets/icons/bx-analyse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/theme/styles/app/_freak.less: -------------------------------------------------------------------------------- 1 | .freakMixin() { 2 | @functions: ~`(function() { 3 | function toColorList(list) { 4 | list = list.slice(1, list.length - 1).split(','); 5 | var ret = []; 6 | for (var i = 0, c = list.length; i < c; i++) { 7 | ret.push(list[i].trim().split(' ')); 8 | } 9 | return ret; 10 | } 11 | 12 | var catchColors; 13 | function _initColor(list) { 14 | if (!catchColors) catchColors = toColorList(list); 15 | } 16 | 17 | this.getColor = function(list, name, position) { 18 | _initColor(list); 19 | var ret = ''; 20 | for (var i = 0, c = catchColors.length; i < c; i++) { 21 | if (catchColors[i][0] === name) { 22 | ret = catchColors[i][position - 1]; 23 | break; 24 | } 25 | } 26 | return ret; 27 | } 28 | })()`; 29 | } 30 | .freakMixin(); -------------------------------------------------------------------------------- /src/assets/theme/styles/app/_functions.less: -------------------------------------------------------------------------------- 1 | // Color contrast 2 | .color-yiq(@color) { 3 | 4 | .yiq-mixin (@v) when (@v >= 150) { 5 | color: #111; 6 | } 7 | .yiq-mixin (@v) when (default()) { 8 | color: #fff; 9 | } 10 | 11 | @r: red(@color); 12 | @g: green(@color); 13 | @b: blue(@color); 14 | 15 | @yiq: ((@r * 299) + (@g * 587) + (@b * 114)) / 1000; 16 | .yiq-mixin(@yiq); 17 | } 18 | 19 | // Basic loop 20 | .for(@adList, @adCode) { 21 | & { 22 | .loop(@adI:1) when (@adI =< length(@adList)) { 23 | @adIndex: @adI - 1; 24 | @adItem: extract(@adList, @adI); 25 | 26 | @adCode(); 27 | 28 | .loop(@adI + 1); 29 | } 30 | .loop(); 31 | } 32 | } 33 | 34 | .for-each(@adList, @adCode) { 35 | & { 36 | .loop(@adI:1) when (@adI =< length(@adList)) { 37 | @adIndex: @adI - 1; 38 | @adItem: extract(@adList, @adI); 39 | @adKey: extract(@adItem, 1); 40 | @adValue: extract(@adItem, 2); 41 | 42 | @adCode(); 43 | 44 | .loop(@adI + 1); 45 | } 46 | .loop(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/_preloader.less: -------------------------------------------------------------------------------- 1 | .preloader { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | overflow: hidden; 8 | background: #49a9ee; 9 | z-index: 9999; 10 | transition: opacity 0.65s; 11 | } 12 | 13 | .preloader-hidden-add { 14 | opacity: 1; 15 | display: block; 16 | } 17 | 18 | .preloader-hidden-add-active { 19 | opacity: 0; 20 | } 21 | 22 | .preloader-hidden { 23 | display: none; 24 | } 25 | 26 | .cs-loader { 27 | position: absolute; 28 | top: 0; 29 | left: 0; 30 | height: 100%; 31 | width: 100%; 32 | } 33 | 34 | .cs-loader-inner { 35 | transform: translateY(-50%); 36 | top: 50%; 37 | position: absolute; 38 | width: 100%; 39 | color: #fff; 40 | text-align: center; 41 | label { 42 | font-size: 20px; 43 | opacity: 0; 44 | display: inline-block; 45 | } 46 | } 47 | 48 | @keyframes lol { 49 | 0% { 50 | opacity: 0; 51 | transform: translateX(-300px); 52 | } 53 | 33% { 54 | opacity: 1; 55 | transform: translateX(0); 56 | } 57 | 66% { 58 | opacity: 1; 59 | transform: translateX(0); 60 | } 61 | 100% { 62 | opacity: 0; 63 | transform: translateX(300px); 64 | } 65 | } 66 | 67 | .cs-loader-inner label { 68 | &:nth-child(6) { 69 | animation: lol 3s infinite ease-in-out; 70 | } 71 | &:nth-child(5) { 72 | animation: lol 3s 100ms infinite ease-in-out; 73 | } 74 | &:nth-child(4) { 75 | animation: lol 3s 200ms infinite ease-in-out; 76 | } 77 | &:nth-child(3) { 78 | animation: lol 3s 300ms infinite ease-in-out; 79 | } 80 | &:nth-child(2) { 81 | animation: lol 3s 400ms infinite ease-in-out; 82 | } 83 | &:nth-child(1) { 84 | animation: lol 3s 500ms infinite ease-in-out; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/_preserve-white-spaces.less: -------------------------------------------------------------------------------- 1 | .preserve-white-spaces-mixin(@enabled) when(@enabled=true) { 2 | // 搜索框与表格 3 | @sf-and-st-white-spacing: @layout-gutter * 2; 4 | 5 | nz-sf + simple-table { 6 | margin-top: @sf-and-st-white-spacing; 7 | } 8 | 9 | // 按钮间间距 10 | @button-and-button-white-spacing: @layout-gutter; 11 | 12 | .ant-btn + .ant-btn, 13 | .ant-btn + nz-popconfirm, 14 | nz-popconfirm + .ant-btn, 15 | nz-popconfirm + nz-popconfirm { 16 | margin-left: @button-and-button-white-spacing; 17 | } 18 | .ant-btn-group { 19 | .ant-btn + .ant-btn { 20 | margin-left: -1px; 21 | } 22 | } 23 | } 24 | 25 | .preserve-white-spaces-mixin(@preserve-white-spaces-enabled); 26 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/button.less: -------------------------------------------------------------------------------- 1 | // Block 2 | .ant-btn__block { 3 | display: block !important; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/card.less: -------------------------------------------------------------------------------- 1 | .ant-card { 2 | margin-bottom: @md; 3 | } 4 | 5 | nz-card { 6 | margin-bottom: @md !important; 7 | } 8 | 9 | // 标题为图片 10 | .ant-card__title-img { 11 | .ant-card-head-title { 12 | img { 13 | vertical-align: middle; 14 | } 15 | } 16 | } 17 | 18 | // 图片式 19 | .ant-card__img { 20 | .ant-card-body { 21 | padding: 0; 22 | } 23 | .img { 24 | max-width: 100%; 25 | vertical-align: middle; 26 | } 27 | } 28 | 29 | // Body no padding 30 | .ant-card__body-nopadding { 31 | .ant-card-body { 32 | padding: 0 !important; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/carousel.less: -------------------------------------------------------------------------------- 1 | nz-carousel[class^='nz-carousel__dot-'] { 2 | .slick-dots li button { 3 | background: @grey-10; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/form.less: -------------------------------------------------------------------------------- 1 | .ant-form-item-label { 2 | em { 3 | color: @grey-6; 4 | } 5 | } 6 | 7 | .ant-form-item-children { 8 | em { 9 | margin-left: @layout-gutter; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/index.less: -------------------------------------------------------------------------------- 1 | @import "./card"; 2 | @import "./modal"; 3 | @import "./carousel"; 4 | @import "./button"; 5 | @import "./input"; 6 | @import "./form"; 7 | @import "./table"; 8 | 9 | @import "./_preserve-white-spaces"; 10 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/input.less: -------------------------------------------------------------------------------- 1 | .content__title { 2 | nz-input-group { 3 | width: auto; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/modal.less: -------------------------------------------------------------------------------- 1 | // Small screen / tablet 2 | @media (min-width: @screen-sm) { 3 | .modal-sm .ant-modal { 4 | max-width: @modal-sm; 5 | width: auto !important; 6 | } 7 | } 8 | 9 | // Medium screen / desktop 10 | @media (min-width: @screen-md) { 11 | .modal-md .ant-modal { 12 | max-width: @modal-md; 13 | width: auto !important; 14 | } 15 | } 16 | 17 | // Large screen / wide desktop 18 | @media (min-width: @screen-lg) { 19 | .modal-lg .ant-modal { 20 | max-width: @modal-lg; 21 | width: auto !important; 22 | } 23 | } 24 | 25 | // Extra extra large screen / large descktop 26 | @media (min-width: @screen-xxl) { 27 | .modal-xl .ant-modal { 28 | max-width: @modal-xl; 29 | width: auto !important; 30 | } 31 | } 32 | 33 | // 自定义模态框时非常有用,主要运用于查看、编辑页 34 | .modal { 35 | &-header { 36 | padding: 16px 24px; 37 | margin: -24px -24px 24px -24px; 38 | border-radius: @border-radius-base @border-radius-base 0 0; 39 | background: @component-background; 40 | border-bottom: @border-width-base @border-style-base @border-color-split; 41 | display: flex; 42 | justify-content: space-between; 43 | } 44 | &-title { 45 | font-size: @font-size-lg; 46 | font-weight: 500; 47 | line-height: 22px; 48 | color: @heading-color; 49 | small { 50 | color: @muted-color; 51 | font-size: 12px; 52 | margin-left: @layout-gutter; 53 | } 54 | } 55 | &-spin { 56 | display: block; 57 | min-height: 150px; 58 | line-height: 150px; 59 | text-align: center; 60 | } 61 | &-footer { 62 | border-top: @border-width-base @border-style-base @border-color-split; 63 | padding: 10px 16px; 64 | text-align: right; 65 | border-radius: 0 0 @border-radius-base @border-radius-base; 66 | margin: 24px -24px -24px -24px; 67 | } 68 | &-include-tabs { 69 | nz-tabset { 70 | margin-top: -16px; 71 | } 72 | } 73 | &-body-nopadding { 74 | .ant-modal-body { 75 | padding: 0 !important; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/antd/table.less: -------------------------------------------------------------------------------- 1 | nz-table { 2 | td { 3 | > img, 4 | .img { 5 | border-radius: @nz-table-img-radius; 6 | vertical-align: middle; 7 | margin-right: @nz-table-img-margin-right; 8 | max-height: @nz-table-img-max-height; 9 | max-width: @nz-table-img-max-width; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/aside.less: -------------------------------------------------------------------------------- 1 | .aside { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | margin-top: @header-hg; 6 | width: @aside-wd; 7 | background-color: @aside-bg; 8 | transition: width 0.2s @layout-ease, translate 0.2s @layout-ease; 9 | z-index: @zindex + 5; 10 | overflow: hidden; 11 | backface-visibility: hidden; 12 | -webkit-overflow-scrolling: touch; 13 | &:after { 14 | content: ''; 15 | position: absolute; 16 | right: 0; 17 | top: 0; 18 | bottom: 0; 19 | border-right: 1px solid @content-heading-border; 20 | } 21 | &-inner { 22 | overflow-x: hidden; 23 | overflow-y: scroll; // margin-right: -17px; 24 | height: 100%; 25 | -webkit-overflow-scrolling: touch; 26 | /*IE10,IE11*/ 27 | -ms-scroll-chaining: chained; 28 | -ms-overflow-style: none; 29 | -ms-content-zooming: zoom; 30 | -ms-scroll-rails: none; 31 | -ms-content-zoom-limit-min: 100%; 32 | -ms-content-zoom-limit-max: 500%; 33 | -ms-scroll-snap-type: proximity; 34 | -ms-scroll-snap-points-x: snapList(100%, 200%, 300%, 400%, 500%); 35 | -ms-overflow-style: none; 36 | &::-webkit-scrollbar { 37 | height: @aside-scrollbar-width; 38 | width: @aside-scrollbar-height; 39 | } 40 | &::-webkit-scrollbar-track { 41 | -webkit-box-shadow: inset 0 0 @aside-scrollbar-width 42 | @aside-scrollbar-track-color; 43 | } 44 | &::-webkit-scrollbar-thumb { 45 | background-color: @aside-scrollbar-thumb-color; 46 | } 47 | } 48 | } 49 | 50 | // Desktop 51 | @media (min-width: @screen-md-min) { 52 | .aside-collapsed { 53 | .aside { 54 | width: @aside-collapsed-wd; 55 | } 56 | } 57 | } 58 | 59 | // Under pad 60 | @media (max-width: @screen-sm-max) { 61 | .aside, 62 | .content { 63 | transition: transform 0.3s ease; 64 | } 65 | .content { 66 | transform: translate3d(@aside-wd, 0, 0); 67 | } 68 | .aside-collapsed { 69 | .aside { 70 | transform: translate3d(-100%, 0, 0); 71 | } 72 | .content { 73 | transform: translateZ(0); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/delon/acl.less: -------------------------------------------------------------------------------- 1 | .acl__hide { 2 | display: none !important; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/delon/form.less: -------------------------------------------------------------------------------- 1 | .sf { 2 | display: block; 3 | // mode 4 | &-search + .ad-st { 5 | margin-top: @layout-gutter * 2; 6 | } 7 | .optional { 8 | color: rgba(0, 0, 0, 0.45); 9 | } 10 | // 固定 label 11 | &-fixed { 12 | display: flex; 13 | nz-form-control, 14 | .ant-form-item-control-wrapper { 15 | flex: 1; 16 | } 17 | } 18 | &-array-container { 19 | nz-card { 20 | margin: 0 16px 0 0; 21 | .remove { 22 | display: none; 23 | position: absolute; 24 | right: -16px; 25 | top: -16px; 26 | font-size: 20px; 27 | text-align: center; 28 | height: 32px; 29 | line-height: 32px; 30 | width: 32px; 31 | background: rgba(0, 0, 0, 0.26); 32 | border-radius: 50%; 33 | cursor: pointer; 34 | i { 35 | color: #fff; 36 | } 37 | } 38 | &:hover { 39 | .remove { 40 | display: block; 41 | } 42 | } 43 | } 44 | } 45 | sf-array { 46 | .add { 47 | margin-right: 16px; 48 | } 49 | } 50 | .checkbox-grid-list { 51 | width: 100%; 52 | display: block; 53 | @media (min-width: 575px) { 54 | nz-col { 55 | margin-top: 8px; 56 | } 57 | } 58 | // https://github.com/NG-ZORRO/ng-zorro-antd/issues/1358 59 | .ant-checkbox-group { 60 | display: block; 61 | } 62 | } 63 | // upload 64 | .ant-upload-select-picture-card i { 65 | font-size: 32px; 66 | color: #999; 67 | } 68 | .ant-upload.ant-upload-drag { 69 | height: 180px; 70 | } 71 | // transfer 72 | .ant-transfer { 73 | &-list { 74 | background: #fff; 75 | &-header { 76 | label { 77 | position: unset; 78 | } 79 | } 80 | } 81 | .ant-btn + .ant-btn { 82 | margin-left: 0; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/delon/index.less: -------------------------------------------------------------------------------- 1 | @import "./form"; 2 | @import "./acl"; 3 | @import "./search-form-pro"; 4 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/delon/search-form-pro.less: -------------------------------------------------------------------------------- 1 | .search { 2 | &__form { 3 | // BUG: https://github.com/NG-ZORRO/ng-zorro-antd/issues/1202 4 | &.ant-form-inline { 5 | .ant-form-item-label, 6 | .ant-form-item-control-wrapper { 7 | padding: 0 !important; 8 | } 9 | } 10 | margin-bottom: 24px; 11 | .ant-form-item { 12 | margin-bottom: 24px; 13 | margin-right: 0; 14 | display: flex; 15 | > .ant-form-item-label { 16 | width: auto; 17 | line-height: 32px; 18 | padding-right: 8px; 19 | } 20 | .ant-form-item-control { 21 | line-height: 32px; 22 | } 23 | } 24 | .ant-form-item-control-wrapper { 25 | flex: 1; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/fullscreen.less: -------------------------------------------------------------------------------- 1 | .fullscreen { 2 | display: block; 3 | height: 100%; 4 | overflow: hidden; 5 | background-color: @fullscreen-bg; 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/index.less: -------------------------------------------------------------------------------- 1 | @import "./_freak"; 2 | @import "./mixins/index"; 3 | @import "./_functions"; 4 | 5 | // antd patch 6 | @import "./antd/index"; 7 | 8 | // Core CSS 9 | @import "./router"; 10 | @import "./type"; 11 | 12 | // Layout 13 | @import "./layout"; 14 | @import "./header"; 15 | @import "./aside"; 16 | @import "./scrollbar"; 17 | 18 | // widgets 19 | @import "./widgets/index"; 20 | 21 | // @delon 22 | @import "./delon/index"; 23 | 24 | // Other 25 | @import "./utils/index"; 26 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/layout.less: -------------------------------------------------------------------------------- 1 | html { 2 | direction: ltr; 3 | height: 100%; // http://updates.html5rocks.com/2013/12/300ms-tap-delay-gone-away 4 | touch-action: manipulation; 5 | } 6 | 7 | html, 8 | body, 9 | app-root { 10 | // overflow-x: hidden; 11 | height: 100%; 12 | } 13 | 14 | body { 15 | color: @text-color; 16 | background-color: @content-bg !important; 17 | } 18 | 19 | .progress-bar-container { 20 | height: 10px; 21 | position: absolute; 22 | top: -10px; 23 | right: 0; 24 | left: 0; 25 | z-index: @zindex + 10; 26 | } 27 | 28 | .wrapper { 29 | position: relative; 30 | width: 100%; 31 | height: auto; 32 | min-height: 100%; 33 | overflow-x: hidden; 34 | .unwrap { 35 | margin-right: -@content-padding; 36 | margin-left: -@content-padding; 37 | @media (max-width: @screen-md-max) { 38 | margin-right: 0; 39 | margin-left: 0; 40 | } 41 | } 42 | } 43 | 44 | .content { 45 | margin: @header-hg @content-padding @content-padding @content-padding; 46 | } 47 | 48 | .content__title { 49 | display: flex; 50 | align-items: center; 51 | justify-content: space-between; 52 | color: #929292; 53 | padding: @content-padding; 54 | padding-top: @content-padding - 12; 55 | padding-bottom: @content-padding - 12; 56 | margin-right: -@content-padding; 57 | margin-left: -@content-padding; 58 | margin-bottom: @content-padding; 59 | background-color: @content-heading-bg; 60 | border-bottom: 1px solid @content-heading-border; 61 | > h1 { 62 | font-size: 18px; 63 | font-weight: normal; 64 | margin-bottom: 0; 65 | > small { 66 | display: block; 67 | font-size: 12px; 68 | color: @muted-color; 69 | } 70 | } 71 | } 72 | 73 | // Desktop 74 | @media (min-width: @screen-md-min) { 75 | .content { 76 | margin-left: (@aside-wd + @content-padding); 77 | } 78 | .aside-collapsed { 79 | .sidebar { 80 | width: @aside-collapsed-wd; 81 | } 82 | .content { 83 | margin-left: (@aside-collapsed-wd + @content-padding); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/login.less: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | min-height: 100%; 5 | background: #f0f2f5; 6 | } 7 | 8 | @media (min-width: 768px){ 9 | .container { 10 | background-image: url(https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg); 11 | background-repeat: no-repeat; 12 | background-position: center 110px; 13 | background-size: 100%; 14 | } 15 | } 16 | 17 | .wrap { 18 | padding: 32px 0; 19 | flex: 1; 20 | } 21 | 22 | @media (min-width: 768px){ 23 | .wrap { 24 | padding: 112px 0 24px; 25 | } 26 | } 27 | 28 | .top { 29 | text-align: center; 30 | } 31 | 32 | .login-logo { 33 | height: 44px; 34 | margin-right: 16px; 35 | } 36 | 37 | img { 38 | vertical-align: middle; 39 | border-style: none; 40 | } 41 | 42 | .title { 43 | font-size: 33px; 44 | color: rgba(0,0,0,.85); 45 | font-family: 'Myriad Pro','Helvetica Neue',Arial,Helvetica,sans-serif; 46 | font-weight: 600; 47 | position: relative; 48 | vertical-align: middle; 49 | } 50 | 51 | .desc { 52 | font-size: 14px; 53 | color: rgba(0,0,0,.45); 54 | margin-top: 12px; 55 | margin-bottom: 40px; 56 | } -------------------------------------------------------------------------------- /src/assets/theme/styles/app/mixins/index.less: -------------------------------------------------------------------------------- 1 | @import "text-truncate"; 2 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/mixins/text-truncate.less: -------------------------------------------------------------------------------- 1 | .text-truncate() { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | white-space: nowrap; 5 | } 6 | 7 | .textOverflow() { 8 | overflow: hidden; 9 | text-overflow: ellipsis; 10 | word-break: break-all; 11 | white-space: nowrap; 12 | } 13 | 14 | .textOverflowMulti(@line: 3, @bg: #fff) { 15 | overflow: hidden; 16 | position: relative; 17 | line-height: 1.5em; 18 | max-height: @line * 1.5em; 19 | text-align: justify; 20 | margin-right: -1em; 21 | padding-right: 1em; 22 | &:before { 23 | background: @bg; 24 | content: '...'; 25 | padding: 0 1px; 26 | position: absolute; 27 | right: 14px; 28 | bottom: 0; 29 | } 30 | &:after { 31 | background: white; 32 | content: ''; 33 | margin-top: 0.2em; 34 | position: absolute; 35 | right: 14px; 36 | width: 1em; 37 | height: 1em; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/router.less: -------------------------------------------------------------------------------- 1 | // https://github.com/angular/angular/issues/9845 2 | // 一种路由切换动画的简单实现 3 | app-layout router-outlet + * { 4 | display: block; 5 | animation-duration: @router-animation-duration; 6 | // animation-fill-mode: both; 7 | animation-name: @router-animation-name; // fadeIn; 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/scrollbar.less: -------------------------------------------------------------------------------- 1 | .scrollbar-mixin(@enabled) when(@enabled=true) { 2 | body::-webkit-scrollbar { 3 | height: @scrollbar-width; 4 | width: @scrollbar-height; 5 | } 6 | 7 | body::-webkit-scrollbar-track { 8 | -webkit-box-shadow: inset 0 0 @scrollbar-width @scrollbar-track-color; 9 | } 10 | 11 | body::-webkit-scrollbar-thumb { 12 | background-color: @scrollbar-thumb-color; 13 | outline: 1px solid #333; 14 | } 15 | } 16 | 17 | .scrollbar-mixin(@scrollbar-enabled); 18 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/type.less: -------------------------------------------------------------------------------- 1 | .h1, 2 | .h2, 3 | .h3 { 4 | small, 5 | .small { 6 | font-size: 65%; 7 | } 8 | } 9 | 10 | .h4, 11 | .h5, 12 | .h6 { 13 | small, 14 | .small { 15 | font-size: 75%; 16 | } 17 | } 18 | 19 | .h1 { font-size: @h1-font-size !important; } 20 | .h2 { font-size: @h2-font-size !important; } 21 | .h3 { font-size: @h3-font-size !important; } 22 | .h4 { font-size: @h4-font-size !important; } 23 | .h5 { font-size: @h5-font-size !important; } 24 | .h6 { font-size: @h6-font-size !important; } 25 | 26 | // List 27 | .list-styled { 28 | padding-left: @ul-ol-margin; 29 | list-style: inherit; 30 | } 31 | 32 | .link-unstyled { 33 | padding-left: 0; 34 | list-style: none; 35 | } 36 | 37 | // Forms 38 | fieldset { 39 | border: none; 40 | } 41 | 42 | // Display 43 | .display-1 { 44 | font-size: 32px; 45 | } 46 | 47 | .display-2 { 48 | font-size: 24px; 49 | } 50 | 51 | .display-3 { 52 | font-size: 20px; 53 | } 54 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/abs.less: -------------------------------------------------------------------------------- 1 | // 中心元素 2 | .abs-center-container { 3 | position: relative; 4 | } 5 | 6 | .abs-center { 7 | overflow: auto; 8 | margin: auto; 9 | position: absolute; 10 | top: 0; 11 | left: 0; 12 | bottom: 0; 13 | right: 0; 14 | &.abs-fixed { 15 | position: fixed; 16 | z-index: 999; 17 | } 18 | &.abs-right { 19 | left: auto; 20 | right: 20px; 21 | text-align: right; 22 | } 23 | &.abs-left { 24 | right: auto; 25 | left: 20px; 26 | text-align: left; 27 | } 28 | } 29 | 30 | @media (max-width: 320px) { 31 | .abs-center { 32 | padding: 0 10px; 33 | } 34 | } 35 | 36 | @media (max-height: @screen-sm) { 37 | .abs-center { 38 | position: relative; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/align.less: -------------------------------------------------------------------------------- 1 | .align-baseline { vertical-align: baseline !important; } // Browser default 2 | .align-top { vertical-align: top !important; } 3 | .align-middle { vertical-align: middle !important; } 4 | .align-bottom { vertical-align: bottom !important; } 5 | .align-text-bottom { vertical-align: text-bottom !important; } 6 | .align-text-top { vertical-align: text-top !important; } 7 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/border.less: -------------------------------------------------------------------------------- 1 | // see https://ant.design/docs/spec/colors#Neutral-color-application 2 | @border-color: @border-color-split; 3 | @border-grids: 4 | sm @border-radius-sm, 5 | md @border-radius-md, 6 | lg @border-radius-lg; 7 | 8 | .border { border: 1px solid @border-color !important; } 9 | 10 | @border-width-list: 0, 1; 11 | .for(@border-width-list, { 12 | .border-css-mixin(@i) when(@i > 0) { 13 | @css-value: ~"@{i}px solid @{border-color}"; 14 | } 15 | .border-css-mixin(@i) when(default()) { 16 | @css-value: 0; 17 | } 18 | .border-css-mixin(@adIndex); 19 | .border-@{adIndex} { border: @css-value !important; } 20 | .border-top-@{adIndex} { border-top: @css-value !important; } 21 | .border-right-@{adIndex} { border-right: @css-value !important; } 22 | .border-bottom-@{adIndex} { border-bottom: @css-value !important; } 23 | .border-left-@{adIndex} { border-left: @css-value !important; } 24 | }); 25 | 26 | // color 27 | .for-each(@colors, { 28 | .border-@{adKey} { 29 | border-color: extract(@adItem, @color-basic-position) !important; 30 | } 31 | }); 32 | 33 | .for-each(@aliasColors, { 34 | .border-@{adKey} { 35 | border-color: ~`getColor("@{colors}", "@{adValue}", @{color-basic-position})` !important; 36 | } 37 | }); 38 | 39 | .border-white { 40 | border-color: #fff !important; 41 | } 42 | 43 | // Border-radius 44 | .for-each(@border-grids, { 45 | .rounded-@{adKey} { border-radius: @adValue !important; } 46 | .rounded-top-left-@{adKey} { border-top-left-radius: @adValue !important; } 47 | .rounded-top-right-@{adKey} { border-top-right-radius: @adValue !important; } 48 | .rounded-bottom-left-@{adKey} { border-bottom-left-radius: @adValue !important; } 49 | .rounded-bottom-right-@{adKey} { border-bottom-right-radius: @adValue !important; } 50 | }); 51 | 52 | .rounded-circle { 53 | border-radius: 50%; 54 | } 55 | 56 | .rounded-0 { 57 | border-radius: 0; 58 | } 59 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/code.less: -------------------------------------------------------------------------------- 1 | // Inline code 2 | code { 3 | padding: 2px 4px; 4 | margin: 0 4px; 5 | font-size: 90%; 6 | background-color: @code-bg; 7 | border: 1px solid @code-border-color; 8 | border-radius: @border-radius-base; 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/display.less: -------------------------------------------------------------------------------- 1 | .d-none { display: none !important; } 2 | .d-block { display: block !important; } 3 | .d-inline-block { display: inline-block !important; } 4 | .d-flex { display: flex !important; } 5 | .d-inline-flex { display: inline-flex !important; } 6 | 7 | .justify-content-start { justify-content: flex-start !important; } 8 | .justify-content-end { justify-content: flex-end !important; } 9 | .justify-content-center { justify-content: center !important; } 10 | .justify-content-between { justify-content: space-between !important; } 11 | .justify-content-around { justify-content: space-around !important; } 12 | 13 | .align-items-start { align-items: flex-start !important; } 14 | .align-items-end { align-items: flex-end !important; } 15 | .align-items-center { align-items: center !important; } 16 | .align-items-baseline { align-items: baseline !important; } 17 | .align-items-stretch { align-items: stretch !important; } 18 | 19 | .align-content-start { align-content: flex-start !important; } 20 | .align-content-end { align-content: flex-end !important; } 21 | .align-content-center { align-content: center !important; } 22 | .align-content-between { align-content: space-between !important; } 23 | .align-content-around { align-content: space-around !important; } 24 | .align-content-stretch { align-content: stretch !important; } 25 | 26 | .align-self-auto { align-self: auto !important; } 27 | .align-self-start { align-self: flex-start !important; } 28 | .align-self-end { align-self: flex-end !important; } 29 | .align-self-center { align-self: center !important; } 30 | .align-self-baseline { align-self: baseline !important; } 31 | .align-self-stretch { align-self: stretch !important; } 32 | 33 | .flex-1 { flex: 1 !important; } 34 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/float.less: -------------------------------------------------------------------------------- 1 | .float-none { float:none !important; } 2 | .float-left { float:left !important; } 3 | .float-right { float:right !important; } 4 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/icon.less: -------------------------------------------------------------------------------- 1 | // Sizing 2 | .icon-sm { font-size: @icon-sm !important; } 3 | .icon-md { font-size: @icon-md !important; } 4 | .icon-lg { font-size: @icon-lg !important; } 5 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/img.less: -------------------------------------------------------------------------------- 1 | .img-fluid { 2 | // Part 1: Set a maximum relative to the parent 3 | max-width: 100%; 4 | // Part 2: Override the height to auto, otherwise images will be stretched 5 | // when setting a width and height attribute on the img element. 6 | height: auto; 7 | } 8 | 9 | .img-fluid { 10 | .img-fluid; 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/index.less: -------------------------------------------------------------------------------- 1 | @import "spacing"; 2 | @import "color"; 3 | @import "border"; 4 | @import "display"; 5 | @import "float"; 6 | @import "position"; 7 | @import "align"; 8 | @import "text"; 9 | @import "icon"; 10 | @import "img"; 11 | @import "responsive"; 12 | @import "width"; 13 | @import "other"; 14 | @import "abs"; 15 | @import "code"; 16 | @import "rotate"; 17 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/other.less: -------------------------------------------------------------------------------- 1 | .point { 2 | cursor: pointer; 3 | } 4 | 5 | .no-resize { 6 | resize: none; 7 | max-width: 100%; 8 | min-width: 100%; 9 | } 10 | 11 | .block-center { 12 | margin: 0 auto; 13 | } 14 | 15 | // Background image 16 | .bg-center { 17 | background-position: center center; 18 | background-size: cover; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/position.less: -------------------------------------------------------------------------------- 1 | .fixed-top { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | left: 0; 6 | z-index: @zindex-fixed; 7 | } 8 | 9 | .fixed-bottom { 10 | position: fixed; 11 | right: 0; 12 | bottom: 0; 13 | left: 0; 14 | z-index: @zindex-fixed; 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/responsive.less: -------------------------------------------------------------------------------- 1 | @media (max-width: @screen-xs-max) { 2 | .hidden-xs { 3 | display: none !important; 4 | } 5 | } 6 | 7 | @media (max-width: @screen-sm-max) { 8 | .hidden-sm { 9 | display: none !important; 10 | } 11 | } 12 | 13 | @media (max-width: @screen-md-max) { 14 | .hidden-md { 15 | display: none !important; 16 | } 17 | } 18 | 19 | @media (max-width: @screen-lg-max) { 20 | .hidden-lg { 21 | display: none !important; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/rotate.less: -------------------------------------------------------------------------------- 1 | @rotate-count: 24; 2 | .rotate-loop (@i) when (@i > 0) { 3 | @num: @i * 15; 4 | .rotate-@{num} { 5 | transform: rotate(~'@{num}deg'); 6 | } 7 | .rotate-loop(@i - 1); 8 | } 9 | 10 | .rotate-loop(@rotate-count); 11 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/spacing.less: -------------------------------------------------------------------------------- 1 | @mp-list: margin m, padding p; 2 | 3 | .loop-mp(@infix, @adKey, @adValue, @i: 1) when (@i =< length(@mp-list)) { 4 | @item: extract(@mp-list, @i); 5 | @abbrev: extract(@item, 2); 6 | @prop: extract(@item, 1); 7 | .@{abbrev}@{infix} { @{prop}: @adValue !important; } 8 | .@{abbrev}t@{infix} { @{prop}-top: @adValue !important; } 9 | .@{abbrev}r@{infix} { @{prop}-right: @adValue !important; } 10 | .@{abbrev}b@{infix} { @{prop}-bottom: @adValue !important; } 11 | .@{abbrev}l@{infix} { @{prop}-left: @adValue !important; } 12 | .@{abbrev}x@{infix} { 13 | @{prop}-right: @adValue !important; 14 | @{prop}-left: @adValue !important; 15 | } 16 | .@{abbrev}y@{infix} { 17 | @{prop}-top: @adValue !important; 18 | @{prop}-bottom: @adValue !important; 19 | } 20 | 21 | .loop-mp(@infix, @adKey, @adValue, @i + 1); 22 | } 23 | 24 | .for-each(@spacings, { 25 | .infix-mixin(@adKey) when(@adKey = 0) { 26 | @infix: 0; 27 | } 28 | .infix-mixin(@adKey) when(default()) { 29 | @infix: ~"-@{adKey}"; 30 | } 31 | .infix-mixin(@adKey); 32 | 33 | .loop-mp(@infix, @adKey, @adValue, 1); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/text.less: -------------------------------------------------------------------------------- 1 | // Alignment 2 | .text-left { text-align: left !important; } 3 | .text-center { text-align: center !important; } 4 | .text-right { text-align: right !important; } 5 | 6 | // Transformation 7 | .text-lowercase { text-transform: lowercase !important; } 8 | .text-uppercase { text-transform: uppercase !important; } 9 | .text-capitalize { text-transform: capitalize !important; } 10 | .text-deleted { text-decoration: line-through; } 11 | 12 | // wrapping 13 | .text-nowrap { white-space: nowrap !important; } 14 | .text-wrap { white-space: pre-wrap !important; } 15 | .text-truncate { .text-truncate(); } 16 | 17 | // Weight and italics 18 | 19 | .font-weight-normal { font-weight: normal; } 20 | .font-weight-bold { font-weight: 700; } 21 | .font-italic { font-style: italic; } 22 | 23 | // Sizing 24 | .text-sm { font-size: @text-sm !important; } 25 | .text-md { font-size: @text-md !important; } 26 | .text-lg { font-size: @text-lg !important; } 27 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/utils/width.less: -------------------------------------------------------------------------------- 1 | .for-each(@widths, { 2 | .width-@{adKey} { width: @adValue !important; } 3 | .max-width-@{adKey} { max-width: @adValue !important; } 4 | .min-width-@{adKey} { min-width: @adValue !important; } 5 | }); 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/badge.less: -------------------------------------------------------------------------------- 1 | .badge { 2 | display: inline-block; 3 | padding: @badge-padding; 4 | font-size: @badge-font-size; 5 | line-height: 20px; 6 | color: @badge-color; 7 | text-align: center; 8 | white-space: nowrap; 9 | vertical-align: baseline; 10 | border-radius: @border-radius-base; 11 | 12 | // Empty badges collapse automatically 13 | &:empty { 14 | display: none; 15 | } 16 | 17 | em { 18 | font-style: normal; 19 | } 20 | 21 | &-dot { 22 | display: inline-block; 23 | width: @layout-gutter; 24 | height: @layout-gutter; 25 | padding: 0; 26 | border-radius: 50%; 27 | > * { 28 | display: none; 29 | } 30 | } 31 | } 32 | 33 | // Colors 34 | .badge-variant(@bg) { 35 | .color-yiq(@bg); 36 | background-color: @bg; 37 | } 38 | 39 | .for-each(@colors, { 40 | .badge-@{adKey} { 41 | .badge-variant(extract(@adItem, @color-basic-position)); 42 | } 43 | }); 44 | 45 | .for-each(@aliasColors, { 46 | @aliasColor: ~`getColor("@{colors}", "@{adValue}", @{color-basic-position})`; 47 | .badge-@{adKey} { 48 | .badge-variant(color(@aliasColor)) 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/form.less: -------------------------------------------------------------------------------- 1 | .search-form { 2 | background: @search-form-bg; 3 | padding: @layout-gutter; 4 | border: @border-width-base @border-style-base @border-color-split; 5 | border-radius: @search-form-radius; 6 | margin-bottom: @layout-gutter * 2; 7 | } 8 | 9 | .form-state-visual-feedback-mixin(@enabled) when(@enabled = true) { 10 | .ng-invalid:not(form) { 11 | input { 12 | &:focus { 13 | box-shadow: 0 0 @outline-blur-size @outline-width fade-out(@error-color, .8); 14 | } 15 | &, 16 | &:hover { 17 | border-color: @error-color; 18 | } 19 | } 20 | } 21 | } 22 | .form-state-visual-feedback-mixin(@form-state-visual-feedback-enabled); 23 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/half-float.less: -------------------------------------------------------------------------------- 1 | @hafl-float-size: 2 | sm @layout-gutter * 8, 3 | md @layout-gutter * 10, 4 | lg @layout-gutter * 12; 5 | 6 | .half-float { 7 | position: relative; 8 | img { 9 | display: block; 10 | max-width: 100%; 11 | height: auto; 12 | } 13 | .half-float-bottom { 14 | position: absolute; 15 | left: 50%; 16 | z-index: 2; 17 | } 18 | 19 | .for-each(@hafl-float-size, { 20 | &.half-float-@{adKey} { 21 | margin-bottom: (@adValue / 2) + 10; 22 | .half-float-bottom { 23 | bottom: -(@adValue / 2); 24 | width: @adValue; 25 | height: @adValue; 26 | margin-left: -(@adValue / 2); 27 | } 28 | } 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/index.less: -------------------------------------------------------------------------------- 1 | @import "./user-block"; 2 | @import "./no-data"; 3 | @import "./placeholder"; 4 | @import "./badge"; 5 | @import "./masonry-grid"; 6 | @import "./half-float"; 7 | @import "./router-progress-bar"; 8 | @import "./table"; 9 | @import "./form"; 10 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/masonry-grid.less: -------------------------------------------------------------------------------- 1 | .row-masonry { 2 | position: relative; 3 | margin: 0; 4 | padding: 0; 5 | width: 100%; 6 | column-gap: @masonry-column-gap; 7 | > .col-masonry { 8 | display: inline-block; 9 | width: 100%; 10 | min-height: 1em; 11 | margin-bottom: 15px; 12 | } 13 | img { 14 | max-width: 100%; 15 | } 16 | } 17 | 18 | .for-each(@grid-breakpoints, { 19 | @media only screen and (min-width: @adValue) { 20 | .loopColumn(@pos: 1) when (@pos < 10) { 21 | .row-masonry-@{adKey}-@{pos} { 22 | column-count: @pos; 23 | columns: @pos; 24 | } 25 | .loopColumn(@pos + 1); 26 | } 27 | .loopColumn(); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/no-data.less: -------------------------------------------------------------------------------- 1 | .no-data { 2 | color: rgba(0, 0, 0, 0.25); 3 | text-align: center; 4 | line-height: 64px; 5 | font-size: 16px; 6 | i { 7 | font-size: 24px; 8 | margin-right: 16px; 9 | position: relative; 10 | top: 3px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/placeholder.less: -------------------------------------------------------------------------------- 1 | // Placeholder container 2 | .box-placeholder { 3 | margin-bottom: @placeholder-margin-vertical; 4 | padding: @placeholder-padding; 5 | border: 1px dashed @placeholder-border; 6 | background: @placeholder-background; 7 | color: @placeholder-color; 8 | } 9 | 10 | // Remove margin from the last-child 11 | .box-placeholder > :last-child { 12 | margin-bottom: 0; 13 | } 14 | 15 | // Variant 16 | .box-placeholder-lg { 17 | padding-top: @placeholder-large-padding-vertical; 18 | padding-bottom: @placeholder-large-padding-vertical; 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/router-progress-bar.less: -------------------------------------------------------------------------------- 1 | .router-progress-bar { 2 | width: 100vw; 3 | height: 4px; 4 | overflow: hidden; 5 | background: fade(#ddd, 40%); 6 | position: fixed; 7 | z-index: @zindex-base + 20; 8 | &::after { 9 | content: ' '; 10 | height: 100%; 11 | width: 33.3vw; 12 | animation: gradcolours 5s steps(1) infinite, loadthird 1s infinite linear; 13 | display: block; 14 | transform-origin: top left; 15 | } 16 | } 17 | 18 | .grad(@hex) { 19 | background: 20 | linear-gradient( 21 | 90deg, 22 | fade(@hex, 0) 0%, 23 | @hex 30%, 24 | @hex 50%, 25 | @hex 70%, 26 | fade(@hex, 0) 100% 27 | ); 28 | } 29 | 30 | @keyframes loadthird { 31 | 0% { transform: translateX(-33.3vw); } 32 | 100% { transform: translateX(100vw); } 33 | } 34 | 35 | @keyframes gradcolours { 36 | 0% { .grad(#e88098); } 37 | 20% { .grad(#84bebe); } 38 | 40% { .grad(#e98724); } 39 | 60% { .grad(#afc94e); } 40 | 80% { .grad(#6297a4); } 41 | } 42 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/table.less: -------------------------------------------------------------------------------- 1 | .simple-table { 2 | display: block; 3 | background-color: @simple-table-bc; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/theme/styles/app/widgets/user-block.less: -------------------------------------------------------------------------------- 1 | @user-block-width: @aside-wd - (@layout-gutter * 6); 2 | @user-block-avatar-hw: @avatar-size-lg; 3 | .user-block { 4 | padding-top: (@layout-gutter * 3); 5 | margin: 0 auto; 6 | display: block !important; 7 | width: @user-block-width; 8 | cursor: pointer; 9 | .user-block-dropdown { 10 | display: flex; 11 | align-items: center; 12 | } 13 | .info { 14 | color: @text-color; 15 | width: @user-block-width - @user-block-avatar-hw - @layout-gutter; 16 | } 17 | .avatar { 18 | margin-right: @layout-gutter; 19 | } 20 | } 21 | 22 | // Desktop 23 | @media (min-width: @screen-md-min) { 24 | .aside-collapsed { 25 | .user-block { 26 | width: @aside-collapsed-wd; 27 | .info { 28 | display: none; 29 | } 30 | .avatar { 31 | margin: 0 auto; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/assets/theme/styles/default.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/lib/style/themes/default.less'; 2 | @import '~ant-design-vue/lib/style/mixins/index.less'; 3 | @import './app/mixins/index'; 4 | @import './variable/color'; 5 | @import './variable/global'; 6 | // widgets 7 | @import './variable/ad-badge'; 8 | @import './variable/ad-masonry'; 9 | @import './variable/ad-modal'; 10 | @import './variable/ad-reuse-tab'; 11 | // alain 12 | @import './variable/alain-layout'; 13 | @import './variable/alain-placeholder'; 14 | @import './variable/alain-form'; 15 | @import './variable/alain-table'; 16 | @import './variable/alain-ng'; 17 | -------------------------------------------------------------------------------- /src/assets/theme/styles/index.less: -------------------------------------------------------------------------------- 1 | // default 2 | @import './default'; 3 | // antd 4 | @import "./_antd"; 5 | // app 6 | @import './app/index'; 7 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/app-icons.less: -------------------------------------------------------------------------------- 1 | .app-icons { 2 | padding: @layout-gutter * 2; 3 | .ant-col-6 { 4 | padding: (@layout-gutter * 2) 0; 5 | border-radius: 4px; 6 | text-align: center; 7 | transition: background-color 300ms; 8 | cursor: pointer; 9 | &:hover { 10 | background-color: #ececec; 11 | } 12 | } 13 | i { 14 | border-radius: 50%; 15 | font-size: 22px; 16 | padding: 15px; 17 | } 18 | small { 19 | font-size: 14px; 20 | padding-top: 4px; 21 | color: #9c9c9c; 22 | display: block; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/aside.less: -------------------------------------------------------------------------------- 1 | @{alain-default-prefix}__aside { 2 | position: absolute; 3 | top: 0; 4 | bottom: 0; 5 | margin-top: @alain-default-header-hg; 6 | width: @alain-default-aside-wd; 7 | background-color: @alain-default-aside-bg; 8 | transition: width 0.2s @alain-default-ease, translate 0.2s @alain-default-ease; 9 | z-index: @zindex + 5; 10 | overflow: hidden; 11 | backface-visibility: hidden; 12 | -webkit-overflow-scrolling: touch; 13 | &:after { 14 | content: ''; 15 | position: absolute; 16 | right: 0; 17 | top: 0; 18 | bottom: 0; 19 | border-right: 1px solid @alain-default-content-heading-border; 20 | } 21 | &-inner { 22 | overflow-x: hidden; 23 | overflow-y: scroll; // margin-right: -17px; 24 | height: 100%; 25 | -webkit-overflow-scrolling: touch; 26 | /*IE10,IE11*/ 27 | -ms-scroll-chaining: chained; 28 | -ms-overflow-style: none; 29 | -ms-content-zooming: zoom; 30 | -ms-scroll-rails: none; 31 | -ms-content-zoom-limit-min: 100%; 32 | -ms-content-zoom-limit-max: 500%; 33 | -ms-scroll-snap-type: proximity; 34 | -ms-scroll-snap-points-x: snapList(100%, 200%, 300%, 400%, 500%); 35 | -ms-overflow-style: none; 36 | &::-webkit-scrollbar { 37 | height: @alain-default-aside-scrollbar-width; 38 | width: @alain-default-aside-scrollbar-height; 39 | } 40 | &::-webkit-scrollbar-track { 41 | -webkit-box-shadow: inset 0 0 @alain-default-aside-scrollbar-width 42 | @alain-default-aside-scrollbar-track-color; 43 | } 44 | &::-webkit-scrollbar-thumb { 45 | background-color: @alain-default-aside-scrollbar-thumb-color; 46 | } 47 | } 48 | } 49 | 50 | // Desktop 51 | @media (min-width: @mobile-min) { 52 | @{alain-default-prefix}__collapsed { 53 | @{alain-default-prefix}__aside { 54 | width: @alain-default-aside-collapsed-wd; 55 | } 56 | } 57 | } 58 | 59 | // Under pad 60 | @media (max-width: @mobile-max) { 61 | @{alain-default-prefix}__aside, 62 | @{alain-default-prefix}__content { 63 | transition: transform 0.3s ease; 64 | } 65 | @{alain-default-prefix}__content { 66 | transform: translate3d(@alain-default-aside-wd, 0, 0); 67 | } 68 | @{alain-default-prefix}__collapsed { 69 | @{alain-default-prefix}__aside { 70 | transform: translate3d(-100%, 0, 0); 71 | } 72 | @{alain-default-prefix}__content { 73 | transform: translateZ(0); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/fix/footer-toolbar.less: -------------------------------------------------------------------------------- 1 | @{footer-toolbar-prefix} { 2 | &__left { 3 | margin-left: @alain-default-aside-wd; 4 | } 5 | } 6 | 7 | @{alain-default-prefix}__collapsed { 8 | @{footer-toolbar-prefix} { 9 | &__left { 10 | margin-left: @alain-default-aside-collapsed-wd; 11 | } 12 | } 13 | } 14 | 15 | @{footer-toolbar-prefix}__body { 16 | @{alain-default-prefix}__content { 17 | margin-bottom: @layout-gutter + @footer-toolbar-height; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/fix/full-content.less: -------------------------------------------------------------------------------- 1 | @{full-content-prefix} { 2 | &__body { 3 | .alain-default__content { 4 | router-outlet + * { 5 | display: block; 6 | height: 100%; 7 | width: 100%; 8 | } 9 | } 10 | .alain-default__content-title { 11 | margin-left: -24px; 12 | } 13 | } 14 | &__opened { 15 | .alain-default__header, 16 | .alain-default__aside, 17 | reuse-tab { 18 | display: none !important; 19 | } 20 | .alain-default__content { 21 | margin: 24px !important; 22 | } 23 | } 24 | &__hidden-title { 25 | .alain-default__content-title, 26 | .page-header { 27 | display: none !important; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/fix/index.less: -------------------------------------------------------------------------------- 1 | @import './sidebar-nav'; 2 | @import './footer-toolbar'; 3 | @import './full-content'; 4 | @import './page-header'; 5 | @import './quick-menu'; 6 | @import './reuse-tab'; 7 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/fix/page-header.less: -------------------------------------------------------------------------------- 1 | @{alain-default-prefix} { 2 | @{page-header-prefix} { 3 | padding: @alain-default-content-padding - 12 @alain-default-content-padding 0 @alain-default-content-padding; 4 | margin-right: -@alain-default-content-padding; 5 | margin-left: -@alain-default-content-padding; 6 | margin-bottom: @alain-default-content-padding; 7 | border-bottom: 1px solid @alain-default-content-heading-border; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/fix/quick-menu.less: -------------------------------------------------------------------------------- 1 | @{quick-menu-prefix} { 2 | @media (max-width: @mobile-max) { 3 | right: -(@alain-default-content-padding + @quick-menu-border-width) !important; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/fix/reuse-tab.less: -------------------------------------------------------------------------------- 1 | @{reuse-tab-prefix} { 2 | margin: 0 -@alain-default-content-padding 0 -@alain-default-content-padding; 3 | } 4 | 5 | @{alain-default-prefix}__fixed { 6 | @{reuse-tab-prefix} { 7 | position: fixed; 8 | top: @alain-default-header-hg; 9 | width: 100%; 10 | z-index: @zindex-fixed + 1; 11 | } 12 | } 13 | 14 | @media (min-width: @mobile-min) { 15 | @{alain-default-prefix}__fixed { 16 | @{reuse-tab-prefix} { 17 | + router-outlet { 18 | display: block; 19 | height: @reuse-tab-height; 20 | } 21 | } 22 | } 23 | } 24 | 25 | @media (max-width: @mobile-max) { 26 | @{alain-default-prefix}__fixed { 27 | @{reuse-tab-prefix} { 28 | position: unset; 29 | margin-top: @reuse-tab-height; 30 | width: initial; 31 | .ant-tabs { 32 | margin: 0 @alain-default-content-padding - @reuse-tab-padding; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/fixed.less: -------------------------------------------------------------------------------- 1 | @{alain-default-prefix}__fixed { 2 | @{alain-default-prefix} { 3 | &__header { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | } 8 | &__aside { 9 | position: fixed; 10 | } 11 | &__content { 12 | margin-top: @alain-default-header-hg; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/index.less: -------------------------------------------------------------------------------- 1 | @import "./variable"; 2 | 3 | @import "./layout"; 4 | @import "./header"; 5 | @import "./aside"; 6 | 7 | @import "./progress-bar"; 8 | @import "./app-icons"; 9 | @import "./user"; 10 | @import "./fix/index"; 11 | 12 | @import "./fixed"; 13 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/layout.less: -------------------------------------------------------------------------------- 1 | html { 2 | direction: ltr; 3 | height: 100%; // http://updates.html5rocks.com/2013/12/300ms-tap-delay-gone-away 4 | touch-action: manipulation; 5 | } 6 | 7 | html, 8 | body, 9 | app-root { 10 | // overflow-x: hidden; 11 | height: 100%; 12 | } 13 | 14 | body { 15 | color: @text-color; 16 | background-color: @alain-default-content-bg; 17 | } 18 | 19 | @{alain-default-prefix} { 20 | display: block; 21 | position: relative; 22 | width: 100%; 23 | height: auto; 24 | min-height: 100%; 25 | overflow-x: hidden; 26 | &__unwrap { 27 | margin-right: -@alain-default-content-padding; 28 | margin-left: -@alain-default-content-padding; 29 | @media (max-width: @mobile-max) { 30 | margin-right: 0; 31 | margin-left: 0; 32 | } 33 | } 34 | &__content { 35 | margin: 0 @alain-default-content-padding @alain-default-content-padding @alain-default-content-padding; 36 | &-title { 37 | display: flex; 38 | align-items: center; 39 | justify-content: space-between; 40 | color: #929292; 41 | padding: @alain-default-content-padding; 42 | padding-top: @alain-default-content-padding - 12; 43 | padding-bottom: @alain-default-content-padding - 12; 44 | margin-right: -@alain-default-content-padding; 45 | margin-left: -@alain-default-content-padding; 46 | margin-bottom: @alain-default-content-padding; 47 | background-color: @alain-default-content-heading-bg; 48 | border-bottom: 1px solid @alain-default-content-heading-border; 49 | > h1 { 50 | font-size: 18px; 51 | font-weight: normal; 52 | margin-bottom: 0; 53 | > small { 54 | display: block; 55 | font-size: 12px; 56 | color: @muted-color; 57 | } 58 | } 59 | } 60 | // fix width 61 | nz-input-group { 62 | width: auto; 63 | } 64 | } 65 | } 66 | 67 | // Desktop 68 | @media (min-width: @mobile-min) { 69 | @{alain-default-prefix}__content { 70 | margin-left: (@alain-default-aside-wd + @alain-default-content-padding); 71 | } 72 | @{alain-default-prefix}__collapsed { 73 | @{alain-default-prefix} { 74 | &__sidebar { 75 | width: @alain-default-aside-collapsed-wd; 76 | } 77 | &__content { 78 | margin-left: (@alain-default-aside-collapsed-wd + @alain-default-content-padding); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/progress-bar.less: -------------------------------------------------------------------------------- 1 | @{alain-default-prefix}__progress-bar { 2 | width: 100vw; 3 | height: 4px; 4 | overflow: hidden; 5 | background: fade(#ddd, 40%); 6 | position: fixed; 7 | z-index: @zindex-base + 20; 8 | &::after { 9 | content: ' '; 10 | height: 100%; 11 | width: 33.3vw; 12 | animation: gradcolours 5s steps(1) infinite, loadthird 1s infinite linear; 13 | display: block; 14 | transform-origin: top left; 15 | } 16 | } 17 | 18 | .grad(@hex) { 19 | background: 20 | linear-gradient( 21 | 90deg, 22 | fade(@hex, 0) 0%, 23 | @hex 30%, 24 | @hex 50%, 25 | @hex 70%, 26 | fade(@hex, 0) 100% 27 | ); 28 | } 29 | 30 | @keyframes loadthird { 31 | 0% { transform: translateX(-33.3vw); } 32 | 100% { transform: translateX(100vw); } 33 | } 34 | 35 | @keyframes gradcolours { 36 | 0% { .grad(#e88098); } 37 | 20% { .grad(#84bebe); } 38 | 40% { .grad(#e98724); } 39 | 60% { .grad(#afc94e); } 40 | 80% { .grad(#6297a4); } 41 | } 42 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/user.less: -------------------------------------------------------------------------------- 1 | @alain-default-user-block-width: @alain-default-aside-wd - (@layout-gutter * 6); 2 | @alain-default-user-block-avatar-hw: @avatar-size-lg; 3 | 4 | @{alain-default-prefix}__aside-user { 5 | padding-top: (@layout-gutter * 3); 6 | margin: 0 auto; 7 | display: block !important; 8 | width: @alain-default-user-block-width; 9 | cursor: pointer; 10 | .ant-dropdown-trigger { 11 | display: flex; 12 | align-items: center; 13 | } 14 | &-info { 15 | color: @text-color; 16 | width: @alain-default-user-block-width - @alain-default-user-block-avatar-hw - @layout-gutter; 17 | } 18 | &-avatar { 19 | margin-right: @layout-gutter; 20 | } 21 | } 22 | 23 | // Desktop 24 | @media (min-width: @mobile-min) { 25 | @{alain-default-prefix}__collapsed { 26 | @{alain-default-prefix}__aside-user { 27 | width: @alain-default-aside-collapsed-wd; 28 | &-info { 29 | display: none; 30 | } 31 | &-avatar { 32 | margin: 0 auto; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/default/variable.less: -------------------------------------------------------------------------------- 1 | @alain-default-prefix: ~'.alain-default'; 2 | @alain-default-zindex: @zindex-base; 3 | @alain-default-ease: cubic-bezier(.25, 0, .15, 1); 4 | @alain-default-header-hg: 64px; 5 | @alain-default-header-bg: @primary-color; 6 | @alain-default-header-padding: @layout-gutter * 2; 7 | @alain-default-header-search-enabled: true; 8 | @alain-default-header-search-height: 34px; 9 | 10 | @alain-default-aside-wd: 200px; 11 | @alain-default-aside-bg: #fff; 12 | @alain-default-aside-scrollbar-width: 0; 13 | @alain-default-aside-scrollbar-height: 0; 14 | @alain-default-aside-scrollbar-track-color: transparent; 15 | @alain-default-aside-scrollbar-thumb-color: transparent; 16 | 17 | @alain-default-aside-nav-fs: 14px; 18 | @alain-default-aside-nav-icon-width: 14px; 19 | @alain-default-aside-nav-padding-top-bottom: @layout-gutter; 20 | @alain-default-aside-nav-text-color: @text-color; 21 | @alain-default-aside-nav-text-hover-color: @primary-color; 22 | @alain-default-aside-nav-group-text-color: @text-color-secondary; 23 | @alain-default-aside-nav-selected-text-color: @primary-color; 24 | @alain-default-aside-nav-selected-bg: #fcfcfc; 25 | @alain-default-aside-nav-item-height: 38px; 26 | @alain-default-aside-collapsed-wd: @layout-gutter * 8; 27 | @alain-default-aside-collapsed-nav-fs: 24px; 28 | 29 | @alain-default-content-heading-bg: #fafbfc; 30 | @alain-default-content-heading-border: #efe3e5; 31 | @alain-default-content-padding: @layout-gutter * 3; 32 | @alain-default-content-bg: #f5f7fa; 33 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/fullscreen/fix/index.less: -------------------------------------------------------------------------------- 1 | @{alain-fullscreen-prefix} { 2 | @import './page-header'; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/fullscreen/fix/page-header.less: -------------------------------------------------------------------------------- 1 | @{page-header-prefix} { 2 | padding: @alain-fullscreen-content-padding-horizontal; 3 | padding-top: @alain-fullscreen-content-padding-horizontal - 12; 4 | padding-bottom: @alain-fullscreen-content-padding-horizontal - 12; 5 | margin-right: -@alain-fullscreen-content-padding-horizontal; 6 | margin-left: -@alain-fullscreen-content-padding-horizontal; 7 | margin-bottom: @alain-fullscreen-content-padding-vertical; 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/fullscreen/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Theme 3 | order: 11 4 | title: Fullscreen Layout 5 | --- 6 | 7 | The fullscreen layout all parameters are prefixed with `@alain-fullscreen-`. 8 | 9 | ## Parameters 10 | 11 | | Name | Default | Description | 12 | | --- | --- | --- | 13 | | `@prefix` | `.alain-fullscreen` | Style name prefix | 14 | | `@bg` | `#f5f7fa` | Background color | 15 | | `@content-padding-vertical` | `0` | Vertical padding | 16 | | `@content-padding-horizontal` | `16px` | Horizontal padding | 17 | 18 | ## Usage 19 | 20 | Import in `src/styles.less`: 21 | 22 | ```less 23 | @import '~@delon/theme/styles/layout/fullscreen/index'; 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/fullscreen/index.less: -------------------------------------------------------------------------------- 1 | @import "./variable"; 2 | 3 | @import "./layout"; 4 | @import "./fix/index"; 5 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/fullscreen/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Theme 3 | order: 11 4 | title: 全屏布局 5 | --- 6 | 7 | 默认布局所有参数都以 `@alain-fullscreen-` 开头。 8 | 9 | ## 参数 10 | 11 | | 名称 | 默认值 | 功能 | 12 | | --- | --- | --- | 13 | | `@prefix` | `.alain-fullscreen` | 布局样式前缀 | 14 | | `@bg` | `#f5f7fa` | 背景色 | 15 | | `@content-padding-vertical` | `0` | 垂直内边距 | 16 | | `@content-padding-horizontal` | `16px` | 水平内边距 | 17 | 18 | ## 使用方式 19 | 20 | 在 `src/styles.less` 引入: 21 | 22 | ```less 23 | @import '~@delon/theme/styles/layout/fullscreen/index'; 24 | ``` 25 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/fullscreen/layout.less: -------------------------------------------------------------------------------- 1 | @{alain-fullscreen-prefix} { 2 | display: block; 3 | background-color: @alain-fullscreen-bg; 4 | padding: @alain-fullscreen-content-padding-vertical @alain-fullscreen-content-padding-horizontal; 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/layout/fullscreen/variable.less: -------------------------------------------------------------------------------- 1 | @alain-fullscreen-prefix: ~'.alain-fullscreen'; 2 | @alain-fullscreen-zindex: @zindex-base; 3 | 4 | @alain-fullscreen-bg: #f5f7fa; 5 | @alain-fullscreen-content-padding-vertical: 0; 6 | @alain-fullscreen-content-padding-horizontal: @layout-gutter * 2; 7 | 8 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/ad-badge.less: -------------------------------------------------------------------------------- 1 | // Badges 2 | @badge-color: @white; 3 | @badge-font-size: @font-size-base; 4 | @badge-padding: 0 8px; 5 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/ad-masonry.less: -------------------------------------------------------------------------------- 1 | // masonry 2 | @masonry-column-gap: @layout-gutter * 2; 3 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/ad-modal.less: -------------------------------------------------------------------------------- 1 | // Modal 2 | @modal-xl: 1200px; 3 | @modal-lg: 900px; 4 | @modal-md: 600px; 5 | @modal-sm: 300px; 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/ad-reuse-tab.less: -------------------------------------------------------------------------------- 1 | // reuse-tab 2 | @reuse-tab-height: 52px; 3 | @reuse-tab-bg: #fff; 4 | @reuse-tab-padding: 8px; 5 | @reuse-tab-border-color: #d9d9d9; 6 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/alain-form.less: -------------------------------------------------------------------------------- 1 | // track control state of visual feedback 2 | @form-state-visual-feedback-enabled: false; 3 | 4 | // search-form 5 | @search-form-bg: #fbfbfb; 6 | @search-form-radius: 4px; 7 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/alain-layout.less: -------------------------------------------------------------------------------- 1 | // =========LAYOUTS========= 2 | @zindex: @zindex-base; 3 | @layout-ease: cubic-bezier(.25, 0, .15, 1); 4 | @header-hg: 64px; 5 | @header-bg: @primary-color; 6 | @header-padding: @layout-gutter * 2; 7 | @header-search-enabled: true; 8 | @header-search-height: 34px; 9 | 10 | @aside-wd: 200px; 11 | @aside-bg: #fff; 12 | @aside-scrollbar-width: 0; 13 | @aside-scrollbar-height: 0; 14 | @aside-scrollbar-track-color: transparent; 15 | @aside-scrollbar-thumb-color: transparent; 16 | 17 | @aside-nav-fs: 14px; 18 | @aside-nav-icon-width: 14px; 19 | @aside-nav-padding-top-bottom: @layout-gutter; 20 | @aside-nav-text-color: @text-color; 21 | @aside-nav-text-hover-color: @primary-color; 22 | @aside-nav-group-text-color: @text-color-secondary; 23 | @aside-nav-selected-text-color: @primary-color; 24 | @aside-nav-selected-bg: #fcfcfc; 25 | @aside-nav-item-height: 38px; 26 | @aside-collapsed-wd: @layout-gutter * 8; 27 | @aside-collapsed-nav-fs: 24px; 28 | 29 | @content-heading-bg: #fafbfc; 30 | @content-heading-border: #efe3e5; 31 | @content-padding: @layout-gutter * 3; 32 | @content-bg: #f5f7fa; 33 | 34 | // fullscreen 35 | @fullscreen-bg: #333; 36 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/alain-ng.less: -------------------------------------------------------------------------------- 1 | // --preserveWhitespaces: false 2 | @preserve-white-spaces-enabled: true; 3 | 4 | // router ant 5 | @router-animation-name: antFadeIn; 6 | @router-animation-duration: 1s; 7 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/alain-placeholder.less: -------------------------------------------------------------------------------- 1 | // placeholder 2 | @placeholder-margin-vertical: 15px; 3 | @placeholder-padding: 20px; 4 | @placeholder-border: #ddd; 5 | @placeholder-background: #fafafa; 6 | @placeholder-color: #444; 7 | @placeholder-large-padding-vertical: 80px; 8 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/alain-table.less: -------------------------------------------------------------------------------- 1 | // table 2 | @nz-table-img-radius: 4px; 3 | @nz-table-img-margin-right: 4px; 4 | @nz-table-img-max-height: 32px; 5 | @nz-table-img-max-width: 32px; 6 | @simple-table-bc: #fff; 7 | @simple-table-pagination-jc: flex-end; 8 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/color.less: -------------------------------------------------------------------------------- 1 | @white: #fff; 2 | @black: #000; 3 | 4 | // grey 5 | @grey-1: color(~`colorPalette("@{grey-6}", 1)`); 6 | @grey-2: color(~`colorPalette("@{grey-6}", 2)`); 7 | @grey-3: color(~`colorPalette("@{grey-6}", 3)`); 8 | @grey-4: color(~`colorPalette("@{grey-6}", 4)`); 9 | @grey-5: color(~`colorPalette("@{grey-6}", 5)`); 10 | @grey-6: #bfbfbf; 11 | @grey-7: color(~`colorPalette("@{grey-6}", 7)`); 12 | @grey-8: color(~`colorPalette("@{grey-6}", 8)`); 13 | @grey-9: color(~`colorPalette("@{grey-6}", 9)`); 14 | @grey-10: color(~`colorPalette("@{grey-6}", 10)`); 15 | 16 | @color-light-position: 2; 17 | @color-basic-position: 3; 18 | @color-dark-position: 4; 19 | @color-no-list: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; 20 | 21 | @colors: 22 | red @red-5 @red-6 @red-7, 23 | volcano @volcano-5 @volcano-6 @volcano-7, 24 | orange @orange-5 @orange-6 @orange-7, 25 | gold @gold-5 @gold-6 @gold-7, 26 | yellow @yellow-5 @yellow-6 @yellow-7, 27 | lime @lime-5 @lime-6 @lime-7, 28 | green @green-5 @green-6 @green-7, 29 | cyan @cyan-5 @cyan-6 @cyan-7, 30 | blue @blue-5 @blue-6 @blue-7, 31 | geekblue @geekblue-5 @geekblue-6 @geekblue-7, 32 | purple @purple-5 @purple-6 @purple-7, 33 | magenta @magenta-5 @magenta-6 @magenta-7, 34 | grey @grey-5 @grey-6 @grey-7; 35 | 36 | // Alias 37 | @aliasColors: 38 | primary blue, 39 | success green, 40 | error red, 41 | warning yellow; 42 | 43 | // Grey patch 44 | @greyColorer: 45 | lighter @grey-3, 46 | darker @grey-9; 47 | -------------------------------------------------------------------------------- /src/assets/theme/styles/variable/global.less: -------------------------------------------------------------------------------- 1 | // Layout Gutter 2 | @layout-gutter: 8px; 3 | 4 | @sm: @layout-gutter; 5 | @md: @layout-gutter * 2; 6 | @lg: @layout-gutter * 3; 7 | 8 | @grid-breakpoints: 9 | xs @screen-xs, 10 | sm @screen-sm, 11 | md @screen-md, 12 | lg @screen-lg, 13 | xl @screen-xl; 14 | 15 | // spaceing 16 | @spacings: 17 | 0 0, 18 | sm @sm, 19 | md @md, 20 | lg @lg; 21 | 22 | // type 23 | @ul-ol-margin: 18px; 24 | 25 | // position 26 | @zindex-base: @zindex-back-top - 1; 27 | @zindex-fixed: @zindex-base; 28 | 29 | // 文本 30 | @text-sm: @font-size-base + 0; // 12px 31 | @text-md: @font-size-base + 2; // 14px 32 | @text-lg: @font-size-base + 4; // 16px 33 | 34 | // icon 35 | @icon-sm: @font-size-base * 2; // 24px 36 | @icon-md: @font-size-base * 4; // 48px 37 | @icon-lg: @font-size-base * 6; // 72px 38 | 39 | // 宽度 40 | @widths: 41 | sm @layout-gutter * 20, // 160px 42 | md @layout-gutter * 30, // 240px 43 | lg @layout-gutter * 40, // 320px 44 | 10 10%, 45 | 20 20%, 46 | 30 30%, 47 | 40 40%, 48 | 50 50%, 49 | 60 60%, 50 | 70 70%, 51 | 80 80%, 52 | 90 90%, 53 | 100 100%; 54 | 55 | // 圆角 56 | @border-radius-md: @border-radius-base; // 4px 57 | @border-radius-lg: @border-radius-base + 2px; // 6px 58 | 59 | @muted-color: @grey-7; 60 | 61 | // Colors 62 | @white: #fff; 63 | @black: #000; 64 | 65 | // scrollbar 66 | @scrollbar-enabled: true; 67 | @scrollbar-width: 6px; 68 | @scrollbar-height: 6px; 69 | @scrollbar-track-color: rgba(0, 0, 0, 0.3); 70 | @scrollbar-thumb-color: #6e6e6e; 71 | 72 | // type 73 | // ========== 74 | 75 | // font-size 76 | // https://ant.design/docs/spec/font-cn#字号 77 | @font-size-large: @font-size-base + 8; // 20px 78 | @font-size-small: @font-size-base; // 12px 79 | 80 | @h1-font-size: @font-size-base + 20; // 32px 81 | @h2-font-size: @font-size-base + 12; // 24px 82 | @h3-font-size: @font-size-base + 8; // 20px 83 | @h4-font-size: @font-size-base + 4; // 16px 84 | @h5-font-size: @font-size-base + 2; // 14px 85 | @h6-font-size: @font-size-base; // 12px 86 | 87 | @enable-all-colors: true; 88 | 89 | // Code 90 | @code-border-color: #eee; 91 | @code-bg: #f7f7f7; 92 | -------------------------------------------------------------------------------- /src/assets/tmp/demo.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/demo.docx -------------------------------------------------------------------------------- /src/assets/tmp/demo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/demo.pdf -------------------------------------------------------------------------------- /src/assets/tmp/demo.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/demo.pptx -------------------------------------------------------------------------------- /src/assets/tmp/demo.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/demo.xlsx -------------------------------------------------------------------------------- /src/assets/tmp/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "Home", 3 | "settings": "Settings", 4 | "profile": "Profile", 5 | "login": "Login", 6 | "logout": "Logout", 7 | "more": "More", 8 | "top-search-ph": "Search for people, file, photos...", 9 | "lock": "Lock", 10 | "fullscreen": "Fullscreen", 11 | "fullscreen-exit": "Exit Fullscreen", 12 | "clear-local-storage": "Clear Local Storage", 13 | "language": "Language", 14 | "shortcut": "Shortcut", 15 | "dashboard": "Dashboard", 16 | "dashboard_v1": "Dashboard", 17 | "dashboard_analysis": "Analysis", 18 | "dashboard_monitor": "Monitor", 19 | "dashboard_workplace": "Workplace", 20 | "widgets": "Widgets", 21 | "main_navigation": "Main Navigation", 22 | "component": "Component", 23 | "style": "Style", 24 | "gridmasonry": "Grid Masonry", 25 | "typography": "Typography", 26 | "colors": "Colors", 27 | "delon": "@Delon", 28 | "dynamic-form": "Dynamic Form", 29 | "simple-table": "Simple table", 30 | "util": "Util", 31 | "print": "Print", 32 | "guard": "Route Guard", 33 | "cache": "Cache", 34 | "qr": "QR", 35 | "acl": "ACL", 36 | "downfile": "Down File", 37 | "xlsx": "Excel", 38 | "zip": "Zip", 39 | "report": "Report", 40 | "relation": "Relation", 41 | "extras": "Extras", 42 | "helpcenter": "Help Center", 43 | "poi": "Poi", 44 | "pro": "Ant Design Pro", 45 | "form": "Form Page", 46 | "basic-form": "Basic Form", 47 | "step-form": "Step Form", 48 | "advanced-form": "Advanced Form", 49 | "pro-list": "List Page", 50 | "pro-table-list": "Table List", 51 | "pro-basic-list": "Basic List", 52 | "pro-card-list": "Card List", 53 | "pro-search": "Search", 54 | "pro-search-article": "Search (Article)", 55 | "pro-search-project": "Search (Project)", 56 | "pro-search-app": "Search (App)", 57 | "pro-profile": "Profile Page", 58 | "pro-profile-basic": "Basic", 59 | "pro-profile-advanced": "Advanced", 60 | "pro-result": "Result Page", 61 | "pro-result-success": "Success", 62 | "pro-result-fail": "Fail", 63 | "pro-exception": "Exception", 64 | "pro-user": "Account", 65 | "pro-login": "Login", 66 | "pro-register": "Register", 67 | "pro-register-result": "Register Result" 68 | } 69 | -------------------------------------------------------------------------------- /src/assets/tmp/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": "主页", 3 | "settings": "设置", 4 | "profile": "个人资料", 5 | "login": "登录", 6 | "logout": "登出", 7 | "more": "更多", 8 | "top-search-ph": "搜索:员工、文件、照片等", 9 | "lock": "锁屏", 10 | "fullscreen": "全屏", 11 | "fullscreen-exit": "退出全屏", 12 | "clear-local-storage": "清理本地缓存", 13 | "language": "语言", 14 | "shortcut": "快捷菜单", 15 | "dashboard": "仪表盘", 16 | "dashboard_v1": "仪表盘", 17 | "dashboard_analysis": "分析页", 18 | "dashboard_monitor": "监控页", 19 | "dashboard_workplace": "工作台", 20 | "widgets": "小部件", 21 | "main_navigation": "主导航", 22 | "component": "组件", 23 | "style": "样式", 24 | "gridmasonry": "瀑布流", 25 | "typography": "字体排印", 26 | "colors": "色彩", 27 | "delon": "@Delon", 28 | "dynamic-form": "动态表单", 29 | "simple-table": "简易表格", 30 | "util": "工具集", 31 | "print": "打印", 32 | "guard": "路由守卫", 33 | "cache": "字典缓存", 34 | "qr": "二维码", 35 | "acl": "基于角色访问控制", 36 | "downfile": "下载文件", 37 | "xlsx": "Excel操作", 38 | "zip": "本地解压缩", 39 | "report": "报表", 40 | "relation": "全屏关系图", 41 | "extras": "扩展", 42 | "helpcenter": "帮助中心", 43 | "poi": "门店", 44 | "pro": "Ant Design Pro", 45 | "form": "表单页", 46 | "basic-form": "基础表单", 47 | "step-form": "分步表单", 48 | "advanced-form": "高级表单", 49 | "pro-list": "列表页", 50 | "pro-table-list": "查询表格", 51 | "pro-basic-list": "标准列表", 52 | "pro-card-list": "卡片列表", 53 | "pro-search": "搜索列表", 54 | "pro-search-article": "搜索列表(文章)", 55 | "pro-search-project": "搜索列表(项目)", 56 | "pro-search-app": "搜索列表(应用)", 57 | "pro-profile": "详情页", 58 | "pro-profile-basic": "基础详情页", 59 | "pro-profile-advanced": "高级详情页", 60 | "pro-result": "结果", 61 | "pro-result-success": "成功", 62 | "pro-result-fail": "失败", 63 | "pro-exception": "异常", 64 | "pro-user": "账户", 65 | "pro-login": "登录", 66 | "pro-register": "注册", 67 | "pro-register-result": "注册结果" 68 | } 69 | -------------------------------------------------------------------------------- /src/assets/tmp/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/1.png -------------------------------------------------------------------------------- /src/assets/tmp/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/2.png -------------------------------------------------------------------------------- /src/assets/tmp/img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/3.png -------------------------------------------------------------------------------- /src/assets/tmp/img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/4.png -------------------------------------------------------------------------------- /src/assets/tmp/img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/5.png -------------------------------------------------------------------------------- /src/assets/tmp/img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/6.png -------------------------------------------------------------------------------- /src/assets/tmp/img/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/avatar.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg1.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg10.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg2.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg3.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg4.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg5.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg6.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg7.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg8.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/bg9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/bg9.jpg -------------------------------------------------------------------------------- /src/assets/tmp/img/half-float-bg-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/assets/tmp/img/half-float-bg-1.jpg -------------------------------------------------------------------------------- /src/components/_util/util.ts: -------------------------------------------------------------------------------- 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/avatar-list/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 | list-style: none; 11 | display: inline-block; 12 | padding: 0; 13 | margin: 0 0 0 8px; 14 | font-size: 0; 15 | } 16 | } 17 | 18 | .@{avatar-list-item-prefix-cls} { 19 | display: inline-block; 20 | font-size: @font-size-base; 21 | margin-left: -8px; 22 | width: @avatar-size-base; 23 | height: @avatar-size-base; 24 | 25 | :global { 26 | .ant-avatar { 27 | border: 1px solid #fff; 28 | cursor: pointer; 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 | 61 | -------------------------------------------------------------------------------- /src/components/avatar-list/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.AvatarItem 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/avatar-list/index.ts: -------------------------------------------------------------------------------- 1 | import AvatarList from './list.vue'; 2 | import './index.less'; 3 | 4 | export default AvatarList; 5 | -------------------------------------------------------------------------------- /src/components/avatar-list/item.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 79 | -------------------------------------------------------------------------------- /src/components/charts/bar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 80 | -------------------------------------------------------------------------------- /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/liquid.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 79 | 80 | 83 | -------------------------------------------------------------------------------- /src/components/charts/mini-area.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 70 | 71 | 74 | -------------------------------------------------------------------------------- /src/components/charts/mini-bar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /src/components/charts/mini-smooth-area.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 56 | 57 | 60 | -------------------------------------------------------------------------------- /src/components/charts/radar.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 81 | 82 | 85 | -------------------------------------------------------------------------------- /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/charts/transfer-bar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 92 | -------------------------------------------------------------------------------- /src/components/count-down/index.ts: -------------------------------------------------------------------------------- 1 | import CountDown from './count-down.vue' 2 | 3 | export default CountDown; 4 | -------------------------------------------------------------------------------- /src/components/detail-list/detail-list-item.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 66 | 67 | 70 | -------------------------------------------------------------------------------- /src/components/detail-list/index.ts: -------------------------------------------------------------------------------- 1 | import DetailList from './detail-list.vue'; 2 | export default DetailList; 3 | -------------------------------------------------------------------------------- /src/components/ellipsis/index.ts: -------------------------------------------------------------------------------- 1 | import Ellipsis from './ellipsis.vue'; 2 | 3 | export default Ellipsis; 4 | -------------------------------------------------------------------------------- /src/components/exception/index.ts: -------------------------------------------------------------------------------- 1 | import ExceptionPage from './exception-page.vue' 2 | export default ExceptionPage 3 | -------------------------------------------------------------------------------- /src/components/exception/type.ts: -------------------------------------------------------------------------------- 1 | const types = { 2 | 403: { 3 | img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg', 4 | title: '403', 5 | desc: '抱歉,你无权访问该页面' 6 | }, 7 | 404: { 8 | img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg', 9 | title: '404', 10 | desc: '抱歉,你访问的页面不存在或仍在开发中' 11 | }, 12 | 500: { 13 | img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg', 14 | title: '500', 15 | desc: '抱歉,服务器出错了' 16 | } 17 | } 18 | 19 | export default types 20 | -------------------------------------------------------------------------------- /src/components/footer-tool-bar/footer-tool-bar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 42 | 43 | 46 | -------------------------------------------------------------------------------- /src/components/footer-tool-bar/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 | width: 100%; 8 | bottom: 0; 9 | right: 0; 10 | height: 56px; 11 | line-height: 56px; 12 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); 13 | background: #fff; 14 | border-top: 1px solid #e8e8e8; 15 | padding: 0 24px; 16 | z-index: 9; 17 | 18 | &:after { 19 | content: ""; 20 | display: block; 21 | clear: both; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/footer-tool-bar/index.ts: -------------------------------------------------------------------------------- 1 | import FooterToolBar from './footer-tool-bar.vue'; 2 | import './index.less' 3 | 4 | export default FooterToolBar 5 | -------------------------------------------------------------------------------- /src/components/global-footer/global-footer.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 42 | 43 | 70 | -------------------------------------------------------------------------------- /src/components/global-footer/index.ts: -------------------------------------------------------------------------------- 1 | import GlobalFooter from './global-footer.vue' 2 | export default GlobalFooter 3 | -------------------------------------------------------------------------------- /src/components/global-header/index.ts: -------------------------------------------------------------------------------- 1 | import GlobalHeader from './global-header.vue' 2 | export default GlobalHeader 3 | -------------------------------------------------------------------------------- /src/components/icon-selector/icon-selector.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 56 | 57 | 76 | -------------------------------------------------------------------------------- /src/components/icon-selector/index.ts: -------------------------------------------------------------------------------- 1 | import IconSelector from './icon-selector.vue' 2 | export default IconSelector 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/menu/index.ts: -------------------------------------------------------------------------------- 1 | import SMenu from './menu' 2 | export default SMenu 3 | -------------------------------------------------------------------------------- /src/components/multi-tab/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 -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/multi-tab/index.ts: -------------------------------------------------------------------------------- 1 | import MultiTab from './multi-tab.vue' 2 | import './index.less' 3 | 4 | export default MultiTab 5 | -------------------------------------------------------------------------------- /src/components/notice-icon/index.ts: -------------------------------------------------------------------------------- 1 | import NoticeIcon from './notice-icon.vue' 2 | 3 | export default NoticeIcon 4 | -------------------------------------------------------------------------------- /src/components/number-info/index.less: -------------------------------------------------------------------------------- 1 | @import "../index"; 2 | 3 | @numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info"; 4 | 5 | .@{numberInfo-prefix-cls} { 6 | 7 | .ant-pro-number-info-subtitle { 8 | color: @text-color-secondary; 9 | font-size: @font-size-base; 10 | height: 22px; 11 | line-height: 22px; 12 | overflow: hidden; 13 | text-overflow: ellipsis; 14 | word-break: break-all; 15 | white-space: nowrap; 16 | } 17 | 18 | .number-info-value { 19 | margin-top: 4px; 20 | font-size: 0; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | word-break: break-all; 24 | white-space: nowrap; 25 | 26 | & > span { 27 | color: @heading-color; 28 | display: inline-block; 29 | line-height: 32px; 30 | height: 32px; 31 | font-size: 24px; 32 | margin-right: 32px; 33 | } 34 | 35 | .sub-total { 36 | color: @text-color-secondary; 37 | font-size: @font-size-lg; 38 | vertical-align: top; 39 | margin-right: 0; 40 | i { 41 | font-size: 12px; 42 | transform: scale(0.82); 43 | margin-left: 4px; 44 | } 45 | :global { 46 | .anticon-caret-up { 47 | color: @red-6; 48 | } 49 | .anticon-caret-down { 50 | color: @green-6; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/number-info/index.ts: -------------------------------------------------------------------------------- 1 | import NumberInfo from './number-info.vue' 2 | 3 | export default NumberInfo 4 | -------------------------------------------------------------------------------- /src/components/number-info/number-info.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 76 | 77 | 80 | -------------------------------------------------------------------------------- /src/components/page-header/index.ts: -------------------------------------------------------------------------------- 1 | import PageHeader from './page-header.vue'; 2 | 3 | export default PageHeader 4 | -------------------------------------------------------------------------------- /src/components/page-loading/index.ts: -------------------------------------------------------------------------------- 1 | import PageLoading from './page-loading'; 2 | 3 | export default PageLoading; 4 | -------------------------------------------------------------------------------- /src/components/page-loading/page-loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spin } from 'ant-design-vue' 2 | import {Component, Prop, Vue,Watch,Emit,Provide,Inject,Mixins} from "vue-property-decorator"; 3 | 4 | @Component({ 5 | components: {}, 6 | }) 7 | export default class PageLoading extends Vue { 8 | 9 | constructor() { 10 | super(); 11 | } 12 | 13 | render () { 14 | return (
15 | 16 |
) 17 | } 18 | } 19 | // export default { 20 | // name: 'PageLoading', 21 | // render () { 22 | // return (
23 | // 24 | //
) 25 | // } 26 | // } 27 | -------------------------------------------------------------------------------- /src/components/result/index.ts: -------------------------------------------------------------------------------- 1 | import Result from './result.vue' 2 | export default Result 3 | -------------------------------------------------------------------------------- /src/components/setting-drawer/index.ts: -------------------------------------------------------------------------------- 1 | import SettingDrawer from './setting-drawer.vue' 2 | export default SettingDrawer 3 | -------------------------------------------------------------------------------- /src/components/setting-drawer/setting-item.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | 40 | 54 | -------------------------------------------------------------------------------- /src/components/tag-select/index.ts: -------------------------------------------------------------------------------- 1 | import TagSelect from './tag-select'; 2 | 3 | export default TagSelect; 4 | -------------------------------------------------------------------------------- /src/components/tools/breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 76 | 77 | 79 | -------------------------------------------------------------------------------- /src/components/tools/head-info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 51 | 52 | 87 | -------------------------------------------------------------------------------- /src/components/tools/logo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 48 | -------------------------------------------------------------------------------- /src/components/tree/index.ts: -------------------------------------------------------------------------------- 1 | import STree from './s-tree'; 2 | 3 | export default STree; 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 | margin-left: 4px; 13 | position: relative; 14 | top: 1px; 15 | 16 | i { 17 | font-size: 12px; 18 | transform: scale(0.83); 19 | } 20 | } 21 | 22 | .item-text { 23 | display: inline-block; 24 | margin-left: 8px; 25 | color: rgba(0,0,0,.85); 26 | } 27 | 28 | .up { 29 | color: @red-6; 30 | } 31 | .down { 32 | color: @green-6; 33 | top: -1px; 34 | } 35 | 36 | &.reverse-color .up { 37 | color: @green-6; 38 | } 39 | &.reverse-color .down { 40 | color: @red-6; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/trend/index.ts: -------------------------------------------------------------------------------- 1 | import Trend from './trend.vue'; 2 | 3 | export default Trend; 4 | -------------------------------------------------------------------------------- /src/components/trend/trend.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 61 | 62 | 65 | -------------------------------------------------------------------------------- /src/config/default-settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目默认配置项 3 | * primaryColor - 默认主题色 4 | * navTheme - sidebar theme ['dark', 'light'] 两种主题 5 | * colorWeak - 色盲模式 6 | * layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 7 | * fixedHeader - 固定 Header : boolean 8 | * fixSiderbar - 固定左侧菜单栏 : boolean 9 | * autoHideHeader - 向下滚动时,隐藏 Header : boolean 10 | * contentWidth - 内容区布局: 流式 | 固定 11 | * 12 | * storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) 13 | * 14 | */ 15 | 16 | export default { 17 | primaryColor: '#1890FF', // primary color of ant design 18 | navTheme: 'dark', // theme for nav menu 19 | layout: 'sidemenu', // nav menu position: sidemenu or topmenu 20 | contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu 21 | fixedHeader: false, // sticky header 22 | fixSiderbar: false, // sticky siderbar 23 | autoHideHeader: false, // auto hide header 24 | colorWeak: false, 25 | multiTab: true, 26 | production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true', 27 | // vue-ls options 28 | storageOptions: { 29 | namespace: 'pro__', // key prefix 30 | name: 'ls', // name variable Vue.[ls] or this.[$ls], 31 | storage: 'local' // storage name session, local, memory 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/core/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import store from '@/store/'; 3 | import { 4 | ACCESS_TOKEN, 5 | DEFAULT_COLOR, 6 | DEFAULT_THEME, 7 | DEFAULT_LAYOUT_MODE, 8 | DEFAULT_COLOR_WEAK, 9 | SIDEBAR_TYPE, 10 | DEFAULT_FIXED_HEADER, 11 | DEFAULT_FIXED_HEADER_HIDDEN, 12 | DEFAULT_FIXED_SIDEMENU, 13 | DEFAULT_CONTENT_WIDTH_TYPE, 14 | DEFAULT_MULTI_TAB, 15 | } from '@/store/mutation-types'; 16 | import config from '@/config/default-settings'; 17 | 18 | 19 | export default function Initializer () { 20 | let v=Vue as any; 21 | store.commit('SET_SIDEBAR_TYPE', v.ls.get(SIDEBAR_TYPE, true)) 22 | store.commit('TOGGLE_THEME', v.ls.get(DEFAULT_THEME, config.navTheme)) 23 | store.commit('TOGGLE_LAYOUT_MODE', v.ls.get(DEFAULT_LAYOUT_MODE, config.layout)) 24 | store.commit('TOGGLE_FIXED_HEADER', v.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader)) 25 | store.commit('TOGGLE_FIXED_SIDERBAR', v.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar)) 26 | store.commit('TOGGLE_CONTENT_WIDTH', v.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth)) 27 | store.commit('TOGGLE_FIXED_HEADER_HIDDEN', v.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader)) 28 | store.commit('TOGGLE_WEAK', v.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak)) 29 | store.commit('TOGGLE_COLOR', v.ls.get(DEFAULT_COLOR, config.primaryColor)) 30 | store.commit('TOGGLE_MULTI_TAB', config.multiTab) 31 | store.commit('SET_TOKEN', v.ls.get(ACCESS_TOKEN)) 32 | 33 | // last step 34 | } 35 | -------------------------------------------------------------------------------- /src/core/icons.ts: -------------------------------------------------------------------------------- 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 { 12 | // bxAnaalyse 13 | } 14 | -------------------------------------------------------------------------------- /src/core/lazy_lib/components_use.ts: -------------------------------------------------------------------------------- 1 | 2 | /* eslint-disable */ 3 | /** 4 | * 该文件是为了按需加载,剔除掉了一些不需要的框架组件。 5 | * 减少了编译支持库包大小 6 | * 7 | * 当需要更多组件依赖时,在该文件加入即可 8 | */ 9 | import Vue from 'vue' 10 | import { 11 | LocaleProvider, 12 | Layout, 13 | Input, 14 | InputNumber, 15 | Button, 16 | Switch, 17 | Radio, 18 | Checkbox, 19 | Select, 20 | Card, 21 | Form, 22 | Row, 23 | Col, 24 | Modal, 25 | Table, 26 | Tabs, 27 | Icon, 28 | Badge, 29 | Popover, 30 | Dropdown, 31 | List, 32 | Avatar, 33 | Breadcrumb, 34 | Steps, 35 | Spin, 36 | Menu, 37 | Drawer, 38 | Tooltip, 39 | Alert, 40 | Tag, 41 | Divider, 42 | DatePicker, 43 | TimePicker, 44 | Upload, 45 | Progress, 46 | Skeleton, 47 | Popconfirm, 48 | message, 49 | notification, 50 | } from 'ant-design-vue'; 51 | // import VueCropper from 'vue-cropper' 52 | 53 | Vue.use(LocaleProvider);; 54 | Vue.use(Layout); 55 | Vue.use(Input); 56 | Vue.use(InputNumber); 57 | Vue.use(Button); 58 | Vue.use(Switch); 59 | Vue.use(Radio); 60 | Vue.use(Checkbox); 61 | Vue.use(Select); 62 | Vue.use(Card); 63 | Vue.use(Form); 64 | Vue.use(Row); 65 | Vue.use(Col); 66 | Vue.use(Modal); 67 | Vue.use(Table); 68 | Vue.use(Tabs); 69 | Vue.use(Icon); 70 | Vue.use(Badge); 71 | Vue.use(Popover); 72 | Vue.use(Dropdown); 73 | Vue.use(List); 74 | Vue.use(Avatar); 75 | Vue.use(Breadcrumb); 76 | Vue.use(Steps); 77 | Vue.use(Spin); 78 | Vue.use(Menu); 79 | Vue.use(Drawer); 80 | Vue.use(Tooltip); 81 | Vue.use(Alert); 82 | Vue.use(Tag); 83 | Vue.use(Divider); 84 | Vue.use(DatePicker); 85 | Vue.use(TimePicker); 86 | Vue.use(Upload); 87 | Vue.use(Progress); 88 | Vue.use(Skeleton); 89 | Vue.use(Popconfirm); 90 | // Vue.use(VueCropper); 91 | Vue.use(notification); 92 | 93 | Vue.prototype.$confirm = Modal.confirm; 94 | Vue.prototype.$message = message; 95 | Vue.prototype.$notification = notification; 96 | Vue.prototype.$info = Modal.info; 97 | Vue.prototype.$success = Modal.success; 98 | Vue.prototype.$error = Modal.error; 99 | Vue.prototype.$warning = Modal.warning; 100 | -------------------------------------------------------------------------------- /src/core/lazy_use.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueStorage from 'vue-ls'; 3 | import config from '@/config/default-settings'; 4 | 5 | // base library 6 | import '@/core/lazy_lib/components_use'; 7 | 8 | import Viser from 'viser-vue'; 9 | 10 | // ext library 11 | import VueClipboard from 'vue-clipboard2'; 12 | 13 | import PermissionHelper from '@/utils/helper/permission'; 14 | 15 | VueClipboard.config.autoSetContainer = true; 16 | 17 | Vue.use(Viser); 18 | 19 | Vue.use(VueStorage, config.storageOptions); 20 | Vue.use(VueClipboard); 21 | Vue.use(PermissionHelper); 22 | -------------------------------------------------------------------------------- /src/core/use.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueStorage from 'vue-ls' 3 | import config from '@/config/default-settings' 4 | 5 | // base library 6 | import Antd from 'ant-design-vue' 7 | import Viser from 'viser-vue' 8 | import VueCropper from 'vue-cropper' 9 | import 'ant-design-vue/dist/antd.less' 10 | 11 | // ext library 12 | import VueClipboard from 'vue-clipboard2' 13 | import PermissionHelper from '@/utils/helper/permission' 14 | // import '@/components/use' 15 | 16 | VueClipboard.config.autoSetContainer = true 17 | 18 | Vue.use(Antd) 19 | Vue.use(Viser) 20 | 21 | Vue.use(VueStorage, config.storageOptions) 22 | Vue.use(VueClipboard) 23 | Vue.use(PermissionHelper) 24 | Vue.use(VueCropper) 25 | -------------------------------------------------------------------------------- /src/layouts/account/account-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Vue } from 'vue-property-decorator'; 2 | import MixinDevice from '@/shared/mixins/mixin-device'; 3 | import RouteView from '../commons/route-view.vue'; 4 | 5 | @Component({ 6 | components: { 7 | RouteView, 8 | }, 9 | }) 10 | export default class AccountLayoutComponent extends MixinDevice { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | 16 | mounted() { 17 | document.body.classList.add('userLayout') 18 | } 19 | 20 | beforeDestroy() { 21 | document.body.classList.remove('userLayout') 22 | } 23 | } -------------------------------------------------------------------------------- /src/layouts/account/account-layout.less: -------------------------------------------------------------------------------- 1 | #userLayout.user-layout-wrapper { 2 | height: 100%; 3 | 4 | &.mobile { 5 | .container { 6 | .main { 7 | max-width: 368px; 8 | width: 98%; 9 | } 10 | } 11 | } 12 | 13 | .container { 14 | width: 100%; 15 | min-height: 100%; 16 | background: #f0f2f5 url(~@/assets/background.svg) no-repeat 50%; 17 | background-size: 100%; 18 | padding: 110px 0 144px; 19 | position: relative; 20 | 21 | a { 22 | text-decoration: none; 23 | } 24 | 25 | .top { 26 | text-align: center; 27 | 28 | .header { 29 | height: 44px; 30 | line-height: 44px; 31 | 32 | .badge { 33 | position: absolute; 34 | display: inline-block; 35 | line-height: 1; 36 | vertical-align: middle; 37 | margin-left: -12px; 38 | margin-top: -10px; 39 | opacity: 0.8; 40 | } 41 | 42 | .logo { 43 | height: 44px; 44 | vertical-align: top; 45 | margin-right: 16px; 46 | border-style: none; 47 | } 48 | 49 | .title { 50 | font-size: 33px; 51 | color: rgba(0, 0, 0, .85); 52 | font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; 53 | font-weight: 600; 54 | position: relative; 55 | top: 2px; 56 | } 57 | } 58 | .desc { 59 | font-size: 14px; 60 | color: rgba(0, 0, 0, 0.45); 61 | margin-top: 12px; 62 | margin-bottom: 40px; 63 | } 64 | } 65 | 66 | .main { 67 | min-width: 260px; 68 | width: 368px; 69 | margin: 0 auto; 70 | } 71 | 72 | .footer { 73 | position: absolute; 74 | width: 100%; 75 | bottom: 0; 76 | padding: 0 16px; 77 | margin: 48px 0 24px; 78 | text-align: center; 79 | 80 | .links { 81 | margin-bottom: 8px; 82 | font-size: 14px; 83 | a { 84 | color: rgba(0, 0, 0, 0.45); 85 | transition: all 0.3s; 86 | &:not(:last-child) { 87 | margin-right: 40px; 88 | } 89 | } 90 | } 91 | .copyright { 92 | color: rgba(0, 0, 0, 0.45); 93 | font-size: 14px; 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/layouts/account/account-layout.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 47 | 48 | -------------------------------------------------------------------------------- /src/layouts/admin/admin-layout.less: -------------------------------------------------------------------------------- 1 | @import '../../components/global.less'; 2 | /* 3 | * The following styles are auto-applied to elements with 4 | * transition="page-transition" when their visibility is toggled 5 | * by Vue.js. 6 | * 7 | * You can easily play with the page transition by editing 8 | * these styles. 9 | */ 10 | 11 | .page-transition-enter { 12 | opacity: 0; 13 | } 14 | 15 | .page-transition-leave-active { 16 | opacity: 0; 17 | } 18 | 19 | .page-transition-enter .page-transition-container, 20 | .page-transition-leave-active .page-transition-container { 21 | -webkit-transform: scale(1.1); 22 | transform: scale(1.1); 23 | } -------------------------------------------------------------------------------- /src/layouts/admin/admin-layout.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 76 | 77 | -------------------------------------------------------------------------------- /src/layouts/blank/blank-layout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | 25 | -------------------------------------------------------------------------------- /src/layouts/commons/route-view.vue: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /src/layouts/index.ts: -------------------------------------------------------------------------------- 1 | import RouteView from './commons/route-view.vue'; 2 | 3 | import AccountLayout from './account/account-layout.vue'; 4 | import AdminLayout from './admin/admin-layout.vue'; 5 | import BlankLayout from './blank/blank-layout.vue'; 6 | 7 | export { RouteView, AccountLayout, AdminLayout, BlankLayout }; 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill' 2 | import Vue from 'vue' 3 | import App from './App.vue' 4 | import router from './router' 5 | import store from './store/' 6 | import { VueAxios } from '@/utils/request' // axios 不建议引入到 Vue 原型链上 7 | 8 | 9 | // mock 10 | import './mock' 11 | 12 | import './core/use' 13 | import bootstrap from './core/bootstrap' 14 | import '@/permission' // permission control 15 | import '@/utils/filter' // global filter 16 | 17 | 18 | Vue.config.productionTip = false 19 | 20 | Vue.use(VueAxios, router) 21 | 22 | new Vue({ 23 | router, 24 | store, 25 | created () { 26 | bootstrap() 27 | }, 28 | render: h => h(App) 29 | }).$mount('#app') 30 | -------------------------------------------------------------------------------- /src/mock/index.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs2' 2 | 3 | // 判断环境不是 prod 或者 preview 是 true 时,加载 mock 服务 4 | if (process.env.NODE_ENV !== 'production' || process.env.VUE_APP_PREVIEW === 'true') { 5 | // 使用同步加载依赖 6 | // 防止 vuex 中的 GetInfo 早于 mock 运行,导致无法 mock 请求返回结果 7 | console.log('mock mounting') 8 | require('./services/auth') 9 | require('./services/user') 10 | require('./services/manage') 11 | require('./services/other') 12 | require('./services/tagCloud') 13 | 14 | Mock.setup({ 15 | timeout: 800 // setter delay time 16 | }) 17 | console.log('mock mounted') 18 | } 19 | -------------------------------------------------------------------------------- /src/mock/services/auth.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs2' 2 | import { builder, getBody } from '../util' 3 | 4 | const username = ['admin', 'user', 'super'] 5 | const password = ['21232f297a57a5a743894a0e4a801fc3', '8914de686ab28dc22f30d3d8e107ff6c'] // admin, ant.design 6 | 7 | const login = (options) => { 8 | const body = getBody(options) 9 | console.log('mock: body', body) 10 | if (!username.includes(body.username) || !password.includes(body.password)) { 11 | return builder({ isLogin: true }, '账户或密码错误', 401) 12 | } 13 | 14 | return builder({ 15 | 'id': Mock.mock('@guid'), 16 | 'name': Mock.mock('@name'), 17 | 'username': 'admin', 18 | 'password': '', 19 | 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png', 20 | 'status': 1, 21 | 'telephone': '', 22 | 'lastLoginIp': '27.154.74.117', 23 | 'lastLoginTime': 1534837621348, 24 | 'creatorId': 'admin', 25 | 'createTime': 1497160610259, 26 | 'deleted': 0, 27 | 'roleId': 'admin', 28 | 'lang': 'zh-CN', 29 | 'token': '4291d7da9005377ec9aec4a71ea837f' 30 | }, '', 200, { 'Custom-Header': Mock.mock('@guid') }) 31 | } 32 | 33 | const logout = () => { 34 | return builder({}, '[测试接口] 注销成功') 35 | } 36 | 37 | const smsCaptcha = () => { 38 | return builder({ captcha: Mock.mock('@integer(10000, 99999)') }) 39 | } 40 | 41 | const twofactor = () => { 42 | return builder({ stepCode: Mock.mock('@integer(0, 1)') }) 43 | } 44 | 45 | Mock.mock(/\/auth\/login/, 'post', login) 46 | Mock.mock(/\/auth\/logout/, 'post', logout) 47 | Mock.mock(/\/account\/sms/, 'post', smsCaptcha) 48 | Mock.mock(/\/auth\/2step-code/, 'post', twofactor) 49 | -------------------------------------------------------------------------------- /src/mock/util.ts: -------------------------------------------------------------------------------- 1 | const responseBody = { 2 | message: '', 3 | timestamp: 0, 4 | result: null, 5 | code: 0, 6 | _status:null, 7 | _headers:null, 8 | 9 | } 10 | 11 | export const builder = (data, message='', code = 0, headers = {}) => { 12 | responseBody.result = data 13 | if (message !== undefined && message !== null) { 14 | responseBody.message = message 15 | } 16 | if (code !== undefined && code !== 0) { 17 | responseBody.code = code 18 | responseBody._status = code 19 | } 20 | if (headers !== null && typeof headers === 'object' && Object.keys(headers).length > 0) { 21 | responseBody._headers = headers 22 | } 23 | responseBody.timestamp = new Date().getTime() 24 | return responseBody 25 | } 26 | 27 | export const getQueryParameters = (options) => { 28 | const url = options.url 29 | const search = url.split('?')[1] 30 | if (!search) { 31 | return {} 32 | } 33 | return JSON.parse('{"' + decodeURIComponent(search) 34 | .replace(/"/g, '\\"') 35 | .replace(/&/g, '","') 36 | .replace(/=/g, '":"') + '"}') 37 | } 38 | 39 | export const getBody = (options) => { 40 | return options.body && JSON.parse(options.body) 41 | } 42 | -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-console */ 2 | 3 | import { register } from 'register-service-worker'; 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB', 11 | ); 12 | }, 13 | cached() { 14 | console.log('Content has been cached for offline use.'); 15 | }, 16 | updated() { 17 | console.log('New content is available; please refresh.'); 18 | }, 19 | offline() { 20 | console.log('No internet connection found. App is running in offline mode.'); 21 | }, 22 | error(error) { 23 | console.error('Error during service worker registration:', error); 24 | }, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import { constantRouterMap } from '@/config/router.config'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | export default new VueRouter({ 8 | mode: 'history', 9 | base: process.env.BASE_URL, 10 | scrollBehavior: () => { y: 0 }, 11 | routes: constantRouterMap, 12 | }); 13 | -------------------------------------------------------------------------------- /src/shared/mixins/app-device-enquire.ts: -------------------------------------------------------------------------------- 1 | import {Component, Prop, Vue,Watch,Emit,Provide,Inject,Mixins} from "vue-property-decorator"; 2 | import { deviceEnquire, DEVICE_TYPE } from '@/utils/device' 3 | import { State, Action, Getter } from "vuex-class"; 4 | 5 | @Component({ 6 | components: {}, 7 | }) 8 | export default class AppDeviceEnquire extends Vue { 9 | 10 | constructor() { 11 | super(); 12 | } 13 | 14 | mounted () { 15 | const { $store } = this 16 | deviceEnquire(deviceType => { 17 | switch (deviceType) { 18 | case DEVICE_TYPE.DESKTOP: 19 | $store.commit('TOGGLE_DEVICE', 'desktop') 20 | $store.dispatch('setSidebar', true) 21 | break 22 | case DEVICE_TYPE.TABLET: 23 | $store.commit('TOGGLE_DEVICE', 'tablet') 24 | $store.dispatch('setSidebar', false) 25 | break 26 | case DEVICE_TYPE.MOBILE: 27 | default: 28 | $store.commit('TOGGLE_DEVICE', 'mobile') 29 | $store.dispatch('setSidebar', true) 30 | break 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/mixins/mixin-device.ts: -------------------------------------------------------------------------------- 1 | import {Component, Prop, Vue, Watch, Emit, Provide, Inject, Mixins} from 'vue-property-decorator'; 2 | import { deviceEnquire, DEVICE_TYPE } from '@/utils/device'; 3 | import { State, Action, Getter } from 'vuex-class'; 4 | import {mapState} from 'vuex'; 5 | import Mixin from './mixin'; 6 | 7 | @Component({ 8 | components: {}, 9 | }) 10 | export default class MixinDevice extends Mixin { 11 | 12 | @State(state=>state.app.device) 13 | public device!:string; 14 | 15 | constructor(){ 16 | super(); 17 | } 18 | 19 | isMobile() { 20 | return this.device === DEVICE_TYPE.MOBILE; 21 | } 22 | 23 | isDesktop() { 24 | return this.device === DEVICE_TYPE.DESKTOP; 25 | } 26 | 27 | isTablet() { 28 | return this.device === DEVICE_TYPE.TABLET; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/shared/mixins/mixin.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Component from 'vue-class-component'; 3 | import { 4 | State, 5 | Getter, 6 | Action, 7 | Mutation, 8 | namespace, 9 | } from 'vuex-class'; 10 | 11 | @Component 12 | export default class Mixin extends Vue { 13 | 14 | @State(state=>state.app.layout) 15 | public layoutMode!:string; 16 | @State(state=>state.app.theme) 17 | public navTheme!:string; 18 | @State(state=>state.app.color) 19 | public primaryColor!:string; 20 | @State(state=>state.app.weak) 21 | public colorWeak!:boolean; 22 | @State(state=>state.app.fixedHeader) 23 | public fixedHeader!:boolean; 24 | @State(state=>state.app.fixSiderbar) 25 | public fixSiderbar!:boolean; 26 | @State(state=>state.app.contentWidth) 27 | public contentWidth!:any; 28 | @State(state=>state.app.autoHideHeader) 29 | public autoHideHeader!:boolean; 30 | @State(state=>state.app.sidebar) 31 | public sidebarOpened!:boolean; 32 | @State(state=>state.app.multiTab) 33 | public multiTab!:boolean; 34 | 35 | 36 | /** */ 37 | @Action 38 | public setSidebar; 39 | 40 | constructor() { 41 | super(); 42 | } 43 | 44 | isTopMenu() { 45 | return this.layoutMode === 'topmenu'; 46 | } 47 | 48 | isSideMenu() { 49 | return !this.isTopMenu(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/shims-ant-design-vue-type.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { Message } from 'ant-design-vue/types/message'; 3 | import { Form } from 'ant-design-vue/types/form/form'; 4 | import {message, Modal, notification} from 'ant-design-vue'; 5 | import VueClipboard from 'vue-clipboard2' 6 | 7 | declare module 'vue/types/vue' { 8 | interface Vue { 9 | $message: Message; 10 | $form: Form; 11 | // @ts-ignore 12 | $confirm: Modal.confirm, 13 | // @ts-ignore 14 | $notification: notification, 15 | // @ts-ignore 16 | $info: Modal.info, 17 | // @ts-ignore 18 | $success: Modal.success, 19 | // @ts-ignore 20 | $error: Modal.error, 21 | // @ts-ignore 22 | $warning: Modal.warning, 23 | // @ts-ignore 24 | $copyText: (...args)=>Promise, 25 | // @ts-ignore 26 | $http:any, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/shims-ant-design-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | declare module 'ant-design-vue' { 3 | const Antd : any; 4 | export default Antd; 5 | } 6 | */ 7 | declare module 'ant-design-vue'; 8 | /* 9 | declare module 'ant-design-vue' { 10 | // import * as AntdVue from '@/ant-desgin-vue/index'; 11 | const Antd : any; 12 | // export * from '@/ant-desgin-vue/index'; 13 | export default Antd; 14 | }*/ 15 | 16 | declare module 'ant-design-vue/lib/LocaleProvider' { 17 | const LocaleProvider : any; 18 | export default LocaleProvider; 19 | } 20 | 21 | 22 | 23 | declare module 'ant-design-vue/lib/locale-provider/zh_CN' { 24 | const zhCN : any; 25 | export default zhCN; 26 | } 27 | 28 | declare module 'ant-design-vue/lib/locale-provider/en_US' { 29 | const enUS : any; 30 | export default enUS; 31 | } 32 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-v-charts.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'v-charts' { 2 | const VCharts : any; 3 | export default VCharts; 4 | } -------------------------------------------------------------------------------- /src/shims-vue-ls-type.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | declare module 'vue/types/vue' { 4 | interface Vue { 5 | $ls: any; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/shims-vue-ls.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare module 'vue-ls' { 3 | const Storage : any; 4 | export default Storage; 5 | } 6 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /src/store/getters.ts: -------------------------------------------------------------------------------- 1 | const getters = { 2 | device: state => state.app.device, 3 | theme: state => state.app.theme, 4 | color: state => state.app.color, 5 | token: state => state.user.token, 6 | avatar: state => state.user.avatar, 7 | nickname: state => state.user.name, 8 | welcome: state => state.user.welcome, 9 | roles: state => state.user.roles, 10 | userInfo: state => state.user.info, 11 | addRouters: state => state.permission.addRouters, 12 | multiTab: state => state.app.multiTab 13 | } 14 | 15 | export default getters 16 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import app from './modules/app' 5 | import user from './modules/user' 6 | import permission from './modules/permission' 7 | import getters from './getters' 8 | import { RootState } from './interface'; 9 | 10 | Vue.use(Vuex) 11 | 12 | export default new Vuex.Store({ 13 | modules: { 14 | app, 15 | user, 16 | permission 17 | }, 18 | state: { 19 | 20 | }, 21 | mutations: { 22 | 23 | }, 24 | actions: { 25 | 26 | }, 27 | getters:getters 28 | }) 29 | -------------------------------------------------------------------------------- /src/store/interface.ts: -------------------------------------------------------------------------------- 1 | export interface RootState { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/store/modules/permission.ts: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '@/config/router.config' 2 | import {ActionContext, ActionTree, GetterTree, Module, MutationTree} from 'vuex'; 3 | import {RootState} from '@/store/interface'; 4 | 5 | /** 6 | * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除 7 | * 8 | * @param permission 9 | * @param route 10 | * @returns {boolean} 11 | */ 12 | function hasPermission(permission, route) { 13 | if (route.meta && route.meta.permission) { 14 | let flag = false; 15 | for (let i = 0, len = permission.length; i < len; i++) { 16 | flag = route.meta.permission.includes(permission[i]); 17 | if (flag) { 18 | return true; 19 | } 20 | } 21 | return false; 22 | } 23 | return true; 24 | } 25 | 26 | /** 27 | * 单账户多角色时,使用该方法可过滤角色不存在的菜单 28 | * 29 | * @param roles 30 | * @param route 31 | * @returns {*} 32 | */ 33 | // eslint-disable-next-line 34 | function hasRole(roles, route) { 35 | if (route.meta && route.meta.roles) { 36 | return route.meta.roles.includes(roles.id); 37 | } else { 38 | return true; 39 | } 40 | } 41 | 42 | function filterAsyncRouter(routerMap, roles) { 43 | const accessedRouters = routerMap.filter( route => { 44 | if (hasPermission(roles.permissionList, route)) { 45 | if (route.children && route.children.length) { 46 | route.children = filterAsyncRouter(route.children, roles); 47 | } 48 | return true; 49 | } 50 | return false; 51 | }); 52 | return accessedRouters; 53 | } 54 | 55 | 56 | export interface IPermission { 57 | routers: any[], 58 | addRouters: any[], 59 | } 60 | 61 | const mutations: MutationTree = { 62 | SET_ROUTERS: (state, routers) => { 63 | state.addRouters = routers; 64 | state.routers = constantRouterMap.concat(routers); 65 | }, 66 | }; 67 | 68 | const actions: ActionTree = { 69 | GenerateRoutes({ commit }, data) { 70 | return new Promise(resolve => { 71 | const { roles } = data; 72 | const accessedRouters = filterAsyncRouter(asyncRouterMap, roles); 73 | commit('SET_ROUTERS', accessedRouters); 74 | resolve(); 75 | }); 76 | }, 77 | }; 78 | 79 | const getters: GetterTree = { 80 | 81 | }; 82 | 83 | const state: IPermission = { 84 | routers: constantRouterMap, 85 | addRouters: [], 86 | }; 87 | 88 | const permission: Module = { 89 | namespaced: false, 90 | state: state, 91 | getters: getters, 92 | actions: actions, 93 | mutations: mutations, 94 | }; 95 | 96 | export default permission; 97 | 98 | -------------------------------------------------------------------------------- /src/store/mutation-types.ts: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'Access-Token' 2 | export const SIDEBAR_TYPE = 'SIDEBAR_TYPE' 3 | export const DEFAULT_THEME = 'DEFAULT_THEME' 4 | export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE' 5 | export const DEFAULT_COLOR = 'DEFAULT_COLOR' 6 | export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK' 7 | export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER' 8 | export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU' 9 | export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN' 10 | export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE' 11 | export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB' 12 | 13 | export const CONTENT_WIDTH_TYPE = { 14 | Fluid: 'Fluid', 15 | Fixed: 'Fixed' 16 | } 17 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/typings.d.ts -------------------------------------------------------------------------------- /src/utils/axios.ts: -------------------------------------------------------------------------------- 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/device.ts: -------------------------------------------------------------------------------- 1 | import enquireJs from 'enquire.js' 2 | 3 | export const DEVICE_TYPE = { 4 | DESKTOP: 'desktop', 5 | TABLET: 'tablet', 6 | MOBILE: 'mobile' 7 | } 8 | 9 | export const deviceEnquire = function (callback) { 10 | const matchDesktop = { 11 | match: () => { 12 | callback && callback(DEVICE_TYPE.DESKTOP) 13 | } 14 | } 15 | 16 | const matchLablet = { 17 | match: () => { 18 | callback && callback(DEVICE_TYPE.TABLET) 19 | } 20 | } 21 | 22 | const matchMobile = { 23 | match: () => { 24 | callback && callback(DEVICE_TYPE.MOBILE) 25 | } 26 | } 27 | 28 | // screen and (max-width: 1087.99px) 29 | enquireJs 30 | .register('screen and (max-width: 576px)', matchMobile) 31 | .register('screen and (min-width: 576px) and (max-width: 1199px)', matchLablet) 32 | .register('screen and (min-width: 1200px)', matchDesktop) 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/domUtil.ts: -------------------------------------------------------------------------------- 1 | export const setDocumentTitle = function (title) { 2 | document.title = title 3 | const ua = navigator.userAgent 4 | // eslint-disable-next-line 5 | const regex = /\bMicroMessenger\/([\d\.]+)/ 6 | if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) { 7 | const i = document.createElement('iframe') 8 | i.src = '/favicon.ico' 9 | i.style.display = 'none' 10 | i.onload = function () { 11 | setTimeout(function () { 12 | i.remove() 13 | }, 9) 14 | } 15 | document.body.appendChild(i) 16 | } 17 | } 18 | 19 | export const domTitle = 'Ant Design Pro' 20 | -------------------------------------------------------------------------------- /src/utils/filter.ts: -------------------------------------------------------------------------------- 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/helper/permission.ts: -------------------------------------------------------------------------------- 1 | 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 | function plugin(Vue) { 14 | if (Vue.installed) { 15 | return 16 | } 17 | 18 | !Vue.prototype.$auth && Object.defineProperties(Vue.prototype, { 19 | $auth: { 20 | get () { 21 | const _this = this 22 | return (permissions) => { 23 | const [permission, action] = permissions.split('.') 24 | const permissionList = _this.$store.getters.roles.permissions 25 | return permissionList.find((val) => { 26 | return val.permissionId === permission 27 | }).actionList.findIndex((val) => { 28 | return val === action 29 | }) > -1 30 | } 31 | } 32 | } 33 | }) 34 | 35 | !Vue.prototype.$enum && Object.defineProperties(Vue.prototype, { 36 | $enum: { 37 | get () { 38 | // const _this = this; 39 | return (val) => { 40 | let result = PERMISSION_ENUM 41 | val && val.split('.').forEach(v => { 42 | result = result && result[v] || null 43 | }) 44 | return result 45 | } 46 | } 47 | } 48 | }) 49 | } 50 | 51 | export default plugin 52 | -------------------------------------------------------------------------------- /src/utils/permissions.ts: -------------------------------------------------------------------------------- 1 | export function actionToObject (json) { 2 | try { 3 | return JSON.parse(json) 4 | } catch (e) { 5 | console.log('err', e.message) 6 | } 7 | return [] 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '@/store' 3 | import { 4 | VueAxios 5 | } from './axios' 6 | import notification from 'ant-design-vue/es/notification' 7 | import { 8 | ACCESS_TOKEN 9 | } from '@/store/mutation-types' 10 | import ls from 'vue-ls'; 11 | import Vue from 'vue' 12 | const v= Vue as any; 13 | 14 | // 创建 axios 实例 15 | const service = axios.create({ 16 | baseURL: '/api', // api base_url 17 | timeout: 6000 // 请求超时时间 18 | }) 19 | 20 | const err = (error) => { 21 | if (error.response) { 22 | const data = error.response.data 23 | const token = v.ls.get(ACCESS_TOKEN) 24 | if (error.response.status === 403) { 25 | notification.error({ 26 | message: 'Forbidden', 27 | description: data.message 28 | }) 29 | } 30 | if (error.response.status === 401 && !(data.result && data.result.isLogin)) { 31 | notification.error({ 32 | message: 'Unauthorized', 33 | description: 'Authorization verification failed' 34 | }) 35 | if (token) { 36 | store.dispatch('Logout').then(() => { 37 | setTimeout(() => { 38 | window.location.reload() 39 | }, 1500) 40 | }) 41 | } 42 | } 43 | } 44 | return Promise.reject(error) 45 | } 46 | 47 | // request interceptor 48 | service.interceptors.request.use(config => { 49 | const token = v.ls.get(ACCESS_TOKEN) 50 | if (token) { 51 | config.headers['Access-Token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改 52 | } 53 | return config 54 | }, err) 55 | 56 | // response interceptor 57 | service.interceptors.response.use((response) => { 58 | return response.data 59 | }, err) 60 | 61 | const installer = { 62 | vm: {}, 63 | install (Vue) { 64 | Vue.use(VueAxios, service) 65 | } 66 | } 67 | 68 | export { 69 | installer as VueAxios, 70 | service as axios 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Set storage 3 | * 4 | * @param name 5 | * @param content 6 | * @param maxAge 7 | */ 8 | export const setStore = (name, content, maxAge = null) => { 9 | if (!(global).window || !name) { 10 | return 11 | } 12 | 13 | if (typeof content !== 'string') { 14 | content = JSON.stringify(content) 15 | } 16 | 17 | const storage = (global).window.localStorage 18 | 19 | storage.setItem(name, content) 20 | if (maxAge && !isNaN(parseInt(maxAge))) { 21 | const timeout = new Date().getTime() / 1000; 22 | storage.setItem(`${name}_expire`, timeout + maxAge) 23 | } 24 | } 25 | 26 | /** 27 | * Get storage 28 | * 29 | * @param name 30 | * @returns {*} 31 | */ 32 | export const getStore = name => { 33 | if (!(global).window || !name) { 34 | return 35 | } 36 | 37 | const content = window.localStorage.getItem(name) 38 | const _expire = parseInt(window.localStorage.getItem(`${name}_expire`)) 39 | 40 | if (_expire) { 41 | const now = new Date().getTime() / 1000; 42 | if (now > _expire) { 43 | return 44 | } 45 | } 46 | 47 | try { 48 | return JSON.parse(content) 49 | } catch (e) { 50 | return content 51 | } 52 | } 53 | 54 | /** 55 | * Clear storage 56 | * 57 | * @param name 58 | */ 59 | export const clearStore = name => { 60 | if (!(global).window || !name) { 61 | return 62 | } 63 | 64 | window.localStorage.removeItem(name) 65 | window.localStorage.removeItem(`${name}_expire`) 66 | } 67 | 68 | /** 69 | * Clear all storage 70 | */ 71 | export const clearAll = () => { 72 | if (!(global).window || !name) { 73 | return 74 | } 75 | 76 | window.localStorage.clear() 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/util.ts: -------------------------------------------------------------------------------- 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:any = 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 = null; 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 | /** 49 | * Remove loading animate 50 | * @param id parent element id or class 51 | * @param timeout 52 | */ 53 | export function removeLoadingAnimate (id = '', timeout = 1500) { 54 | if (id === '') { 55 | return 56 | } 57 | setTimeout(() => { 58 | document.body.removeChild(document.getElementById(id)) 59 | }, timeout) 60 | } 61 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/account/login/login.less: -------------------------------------------------------------------------------- 1 | .user-layout-login { 2 | label { 3 | font-size: 14px; 4 | } 5 | 6 | .getCaptcha { 7 | display: block; 8 | width: 100%; 9 | height: 40px; 10 | } 11 | 12 | .forge-password { 13 | font-size: 14px; 14 | } 15 | 16 | button.login-button { 17 | padding: 0 15px; 18 | font-size: 16px; 19 | height: 40px; 20 | width: 100%; 21 | } 22 | 23 | .user-login-other { 24 | text-align: left; 25 | margin-top: 24px; 26 | line-height: 22px; 27 | 28 | .item-icon { 29 | font-size: 24px; 30 | color: rgba(0, 0, 0, 0.2); 31 | margin-left: 16px; 32 | vertical-align: middle; 33 | cursor: pointer; 34 | transition: color 0.3s; 35 | 36 | &:hover { 37 | color: #1890ff; 38 | } 39 | } 40 | 41 | .register { 42 | float: right; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/views/dashboard/demo.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Vue } from "vue-property-decorator"; 2 | 3 | 4 | 5 | @Component({ 6 | components: {}, 7 | }) 8 | export default class DemoComponent extends Vue { 9 | 10 | myName: string = 'by. 玩双截棍的熊猫'; 11 | 12 | constructor() { 13 | super(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/views/dashboard/demo.less: -------------------------------------------------------------------------------- 1 | .demo{ 2 | color: #2eabff; 3 | } -------------------------------------------------------------------------------- /src/views/dashboard/demo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/dashboard/monitor.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Vue } from "vue-property-decorator"; 2 | 3 | 4 | 5 | @Component({ 6 | components: {}, 7 | }) 8 | export default class MonitorComponent extends Vue { 9 | 10 | myName: string = 'by. 玩双截棍的熊猫'; 11 | 12 | constructor() { 13 | super(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/views/dashboard/monitor.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihango/ant-design-pro-vue-ts/e1844d4f9dec40d731fbfc2d32a14b03629c5360/src/views/dashboard/monitor.less -------------------------------------------------------------------------------- /src/views/dashboard/monitor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | module.exports = (on, config) => { 4 | return Object.assign({}, config, { 5 | fixturesFolder: 'tests/e2e/fixtures', 6 | integrationFolder: 'tests/e2e/specs', 7 | screenshotsFolder: 'tests/e2e/screenshots', 8 | videosFolder: 'tests/e2e/videos', 9 | supportFile: 'tests/e2e/support/index.ts' 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import HelloWorld from '@/components/HelloWorld.vue'; 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message'; 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg }, 9 | }); 10 | expect(wrapper.text()).toMatch(msg); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": false, 6 | "declaration": false, 7 | "strictNullChecks": false, 8 | "sourceMap": true, 9 | "jsx": "preserve", 10 | "jsxFactory": "h", 11 | "importHelpers": true, 12 | "moduleResolution": "node", 13 | "experimentalDecorators": true, 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "baseUrl": ".", 17 | "types": [ 18 | "node", 19 | "jest" 20 | ], 21 | "paths": { 22 | "@/*": [ 23 | "src/*" 24 | ] 25 | }, 26 | "lib": [ 27 | "esnext", 28 | "dom", 29 | "dom.iterable", 30 | "scripthost" 31 | ] 32 | }, 33 | "include": [ 34 | "src/**/*.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "tests/**/*.ts", 38 | "tests/**/*.tsx" 39 | ], 40 | "exclude": [ 41 | "node_modules" 42 | ] 43 | } --------------------------------------------------------------------------------