Repository: jzfai/vue3-admin-ts Branch: master Commit: d6cc2eb2bb79 Files: 212 Total size: 277.3 KB Directory structure: gitextract_0_klg5yg/ ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .husky/ │ ├── commit-msg │ └── pre-commit ├── .npmrc ├── .prettierrc ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── README.md ├── eslintrc/ │ ├── .eslintrc-auto-import.json │ └── eslint-config.cjs ├── index.html ├── mock/ │ ├── example.ts │ └── user.ts ├── optimize-include.ts ├── package.json ├── src/ │ ├── App.vue │ ├── api/ │ │ └── user.ts │ ├── components/ │ │ ├── ElSvgIcon.vue │ │ ├── TestUnit.vue │ │ └── __tests__/ │ │ └── el-svgIcon.test.jsx │ ├── directives/ │ │ ├── button-codes.ts │ │ ├── codes-permission.ts │ │ ├── index.ts │ │ ├── lang.ts │ │ └── roles-permission.ts │ ├── hooks/ │ │ ├── use-common.ts │ │ ├── use-element.ts │ │ ├── use-error-log.ts │ │ ├── use-layout.ts │ │ ├── use-permission.ts │ │ ├── use-self-router.ts │ │ └── use-table.ts │ ├── icons/ │ │ └── SvgIcon.vue │ ├── lang/ │ │ ├── en.ts │ │ ├── index.ts │ │ └── zh.ts │ ├── layout/ │ │ ├── app-main/ │ │ │ ├── Breadcrumb.vue │ │ │ ├── Hamburger.vue │ │ │ ├── Navbar.vue │ │ │ ├── TagsView.vue │ │ │ ├── component/ │ │ │ │ ├── LangSelect.vue │ │ │ │ ├── ScreenFull.vue │ │ │ │ ├── ScreenLock.vue │ │ │ │ ├── SizeSelect.vue │ │ │ │ └── ThemeSelect.vue │ │ │ └── index.vue │ │ ├── index.vue │ │ └── sidebar/ │ │ ├── Link.vue │ │ ├── Logo.vue │ │ ├── MenuIcon.vue │ │ ├── SidebarItem.vue │ │ └── index.vue │ ├── lib/ │ │ ├── el-svg-icon.ts │ │ └── element-plus.ts │ ├── main.ts │ ├── mock-prod-server.ts │ ├── permission.ts │ ├── plugins/ │ │ └── vite-plugin-setup-extend/ │ │ └── index.ts │ ├── router/ │ │ ├── index.ts │ │ └── modules/ │ │ └── basic-demo.ts │ ├── settings.ts │ ├── store/ │ │ ├── basic.ts │ │ ├── config.ts │ │ └── tags-view.ts │ ├── styles/ │ │ ├── index.scss │ │ ├── init-loading.css │ │ ├── project-style.scss │ │ ├── reset-elemenet-plus.scss │ │ ├── scss-suger.scss │ │ └── transition.scss │ ├── theme/ │ │ ├── base/ │ │ │ ├── custom/ │ │ │ │ └── ct-css-vars.scss │ │ │ ├── element-plus/ │ │ │ │ ├── button.scss │ │ │ │ ├── checkbox.scss │ │ │ │ ├── css-vars.scss │ │ │ │ ├── form.scss │ │ │ │ ├── pagination.scss │ │ │ │ ├── redio.scss │ │ │ │ ├── table.scss │ │ │ │ └── var.scss │ │ │ └── index.scss │ │ ├── china-red/ │ │ │ ├── custom/ │ │ │ │ └── ct-css-vars.scss │ │ │ ├── element-plus/ │ │ │ │ ├── button.scss │ │ │ │ ├── checkbox.scss │ │ │ │ ├── css-vars.scss │ │ │ │ ├── form.scss │ │ │ │ ├── pagination.scss │ │ │ │ ├── redio.scss │ │ │ │ ├── table.scss │ │ │ │ └── var.scss │ │ │ └── index.scss │ │ ├── dark/ │ │ │ ├── custom/ │ │ │ │ └── ct-css-vars.scss │ │ │ ├── element-plus/ │ │ │ │ ├── button.scss │ │ │ │ ├── checkbox.scss │ │ │ │ ├── css-vars.css │ │ │ │ ├── css-vars.scss │ │ │ │ ├── form.scss │ │ │ │ ├── pagination.scss │ │ │ │ ├── redio.scss │ │ │ │ ├── table.scss │ │ │ │ └── var.scss │ │ │ └── index.scss │ │ ├── index.css │ │ ├── index.scss │ │ ├── lighting/ │ │ │ ├── custom/ │ │ │ │ └── ct-css-vars.scss │ │ │ ├── element-plus/ │ │ │ │ ├── button.scss │ │ │ │ ├── checkbox.scss │ │ │ │ ├── css-vars.css │ │ │ │ ├── css-vars.scss │ │ │ │ ├── form.scss │ │ │ │ ├── pagination.scss │ │ │ │ ├── redio.scss │ │ │ │ ├── table.scss │ │ │ │ └── var.scss │ │ │ └── index.scss │ │ ├── mixins/ │ │ │ ├── _var.scss │ │ │ ├── config.scss │ │ │ ├── function.scss │ │ │ └── mixins.scss │ │ └── utils/ │ │ ├── change-theme.ts │ │ └── index.ts │ ├── utils/ │ │ ├── axios-req.ts │ │ ├── bus.ts │ │ └── common-util.ts │ └── views/ │ ├── basic-demo/ │ │ ├── hook/ │ │ │ └── index.vue │ │ ├── keep-alive/ │ │ │ ├── index.vue │ │ │ ├── second-child.vue │ │ │ ├── second-keep-alive.vue │ │ │ ├── tab-keep-alive.vue │ │ │ └── third-child.vue │ │ ├── mock/ │ │ │ └── index.vue │ │ ├── parent-children/ │ │ │ ├── Children.vue │ │ │ ├── SubChildren.vue │ │ │ └── index.vue │ │ ├── pinia/ │ │ │ └── index.vue │ │ ├── svg-icon/ │ │ │ └── index.vue │ │ ├── vue3-template/ │ │ │ └── Vue3Template.vue │ │ └── worker/ │ │ └── index.vue │ ├── dashboard/ │ │ └── index.vue │ ├── error-page/ │ │ ├── 401.vue │ │ └── 404.vue │ ├── login/ │ │ └── index.vue │ ├── nested/ │ │ ├── menu1/ │ │ │ ├── index.vue │ │ │ ├── menu1-1/ │ │ │ │ └── index.vue │ │ │ ├── menu1-2/ │ │ │ │ ├── index.vue │ │ │ │ ├── menu1-2-1/ │ │ │ │ │ └── index.vue │ │ │ │ └── menu1-2-2/ │ │ │ │ └── index.vue │ │ │ └── menu1-3/ │ │ │ └── index.vue │ │ └── menu2/ │ │ └── index.vue │ ├── redirect/ │ │ └── index.tsx │ └── setting-switch/ │ ├── SettingSwitch.vue │ └── index.vue ├── ts-out-dir/ │ ├── package.json │ └── src/ │ ├── api/ │ │ ├── user.d.ts │ │ └── user.js │ ├── directives/ │ │ ├── button-codes.d.ts │ │ ├── button-codes.js │ │ ├── codes-permission.d.ts │ │ ├── codes-permission.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── roles-permission.d.ts │ │ └── roles-permission.js │ ├── hooks/ │ │ ├── use-common.d.ts │ │ ├── use-common.js │ │ ├── use-element.d.ts │ │ ├── use-element.js │ │ ├── use-error-log.d.ts │ │ ├── use-error-log.js │ │ ├── use-layout.d.ts │ │ ├── use-layout.js │ │ ├── use-permission.d.ts │ │ ├── use-permission.js │ │ ├── use-self-router.d.ts │ │ ├── use-self-router.js │ │ ├── use-table.d.ts │ │ └── use-table.js │ ├── lib/ │ │ ├── element-plus.d.ts │ │ └── element-plus.js │ ├── main.d.ts │ ├── main.js │ ├── permission.d.ts │ ├── permission.js │ ├── router/ │ │ ├── index.d.ts │ │ └── index.js │ ├── settings.d.ts │ ├── settings.js │ ├── store/ │ │ ├── basic.d.ts │ │ ├── basic.js │ │ ├── tagsView.d.ts │ │ └── tagsView.js │ ├── utils/ │ │ ├── axios-req.d.ts │ │ ├── axios-req.js │ │ ├── bus.d.ts │ │ ├── bus.js │ │ ├── common-util.d.ts │ │ └── common-util.js │ └── views/ │ └── redirect/ │ ├── index.d.ts │ └── index.jsx ├── tsconfig.base.json ├── tsconfig.json ├── typings/ │ ├── auto-imports.d.ts │ ├── basic.d.ts │ ├── components.d.ts │ ├── env.d.ts │ ├── global.d.ts │ └── shims-vue.d.ts ├── vite.config.ts ├── vitest.config.ts └── vitest.setup.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ ================================================ FILE: .eslintignore ================================================ public node_modules .history .husky dist *.d.ts ================================================ FILE: .eslintrc.json ================================================ { "root": true, "extends": ["./eslintrc/eslint-config.cjs", "./eslintrc/.eslintrc-auto-import.json"] } ================================================ FILE: .gitignore ================================================ # compiled output /dist /dist-ssr /node_modules #lock pnpm-lock.yaml # Logs logs *.log npm-debug.log* pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json # Other .history *.local yarn* pnpm* #.eslintrc-auto-import.json #auto-imports.d.ts #components.d.ts stats.html ================================================ FILE: .husky/commit-msg ================================================ #!/bin/sh #. "$(dirname "$0")/_/husky.sh" #在项目中我们会使用commit-msg这个git hook来校验我们commit时添加的备注信息是否符合规范。在以前的我们通常是这样配置: #--no-install 参数表示强制npx使用项目中node_modules目录中的commitlint包(如果需要开启,注意:需要安装npx) #npx --no-install commitlint --edit $1 ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" #推送之前运行eslint检查 npm run lint #推送之前运行单元测试检查 #npm run test:unit ================================================ FILE: .npmrc ================================================ shamefully-hoist=true strict-peer-dependencies=false ###aliyun address registry = https://registry.npmmirror.com ================================================ FILE: .prettierrc ================================================ { "useTabs": false, "tabWidth": 2, "printWidth": 120, "singleQuote": true, "trailingComma": "none", "bracketSpacing": true, "semi": false, "htmlWhitespaceSensitivity": "ignore" } ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": ["johnsoncodehk.volar", "esbenp.prettier-vscode","dbaeumer.vscode-eslint"] } ================================================ FILE: .vscode/settings.json ================================================ { "editor.defaultFormatter": "esbenp.prettier-vscode", "npm.packageManager": "yarn" } ================================================ FILE: README.md ================================================ # vue3-admin-ts the typescript version of vue3 admin template suggestion the Node.js >= v16.20 [Recommended node](https://nodejs.org/download/release/v16.20.2/) ## Documents - [Official Documentation](https://github.jzfai.top/vue3-admin-doc/) - [中文官网](https://github.jzfai.top/vue3-admin-cn-doc/) ## Online experience [Access address](https://github.jzfai.top/vue3-admin-ts) [国内体验地址](https://github.jzfai.top/vue3-admin-ts) ## Related items The framework is available in js,ts, plus and electron versions - js version:[vue3-admin-template](https://github.com/jzfai/vue3-admin-template.git) -- basic version - ts version:[vue3-element-ts](https://github.com/jzfai/vue3-admin-ts.git) - ts version for plus:[vue3-element-plus](https://github.com/jzfai/vue3-admin-plus.git) - ts version for electron:[vue3-element-electron](https://github.com/jzfai/vue3-admin-electron.git) - java Micro-service background data:[micro-service-plus](https://github.com/jzfai/micro-service-plus) ## Build Setup ```bash # clone the project git clone https://github.com/jzfai/vue3-admin-ts.git # enter the project directory cd vue3-admin-ts # pnpm address https://pnpm.io/zh/motivation # install dependency(Recommend use pnpm) # you can use "npm -g i pnpm@7.9.0" to install pnpm pnpm i # develop pnpm run dev ``` ## Build ```bash # build for test environment pnpm run build-test # build for production environment pnpm run build ``` ## Others ```bash # preview the release environment effect pnpm run preview # code format check pnpm run lint ``` ## Browsers support Note: Vue3 is not supported the Internet Explorer ## Discussion and Communication [WeChat group](https://github.jzfai.top/file/images/wx-groud.png) ================================================ FILE: eslintrc/.eslintrc-auto-import.json ================================================ { "globals": { "EffectScope": true, "axiosReq": true, "bus": true, "buttonCodes": true, "casHandleChange": true, "cloneDeep": true, "closeElLoading": true, "codesPermission": true, "commonUtil": true, "computed": true, "copyValueToClipboard": true, "createApp": true, "customRef": true, "defineAsyncComponent": true, "defineComponent": true, "directives": true, "effectScope": true, "elConfirm": true, "elConfirmNoCancelBtn": true, "elLoading": true, "elMessage": true, "elNotify": true, "filterAsyncRouter": true, "filterAsyncRouterByCodes": true, "filterAsyncRoutesByMenuList": true, "filterAsyncRoutesByRoles": true, "freshRouter": true, "getCurrentInstance": true, "getCurrentScope": true, "getLangInstance": true, "getQueryParam": true, "h": true, "inject": true, "isExternal": true, "isProxy": true, "isReactive": true, "isReadonly": true, "isRef": true, "lang": true, "langTitle": true, "markRaw": true, "nextTick": true, "onActivated": true, "onBeforeMount": true, "onBeforeRouteLeave": true, "onBeforeRouteUpdate": true, "onBeforeUnmount": true, "onBeforeUpdate": true, "onDeactivated": true, "onErrorCaptured": true, "onMounted": true, "onRenderTracked": true, "onRenderTriggered": true, "onScopeDispose": true, "onServerPrefetch": true, "onUnmounted": true, "onUpdated": true, "progressClose": true, "progressStart": true, "provide": true, "reactive": true, "readonly": true, "ref": true, "resetRouter": true, "resetState": true, "resizeHandler": true, "resolveComponent": true, "resolveDirective": true, "rolesPermission": true, "routeInfo": true, "routerBack": true, "routerPush": true, "routerReplace": true, "shallowReactive": true, "shallowReadonly": true, "shallowRef": true, "sleepTimeout": true, "storeToRefs": true, "toRaw": true, "toRef": true, "toRefs": true, "triggerRef": true, "unref": true, "useAttrs": true, "useBasicStore": true, "useConfigStore": true, "useCssModule": true, "useCssVars": true, "useElement": true, "useErrorLog": true, "useLink": true, "useRoute": true, "useRouter": true, "useSlots": true, "useTable": true, "useTagsViewStore": true, "watch": true, "watchEffect": true, "watchPostEffect": true, "watchSyncEffect": true } } ================================================ FILE: eslintrc/eslint-config.cjs ================================================ // eslint-disable-next-line @typescript-eslint/no-var-requires const { defineConfig } = require('eslint-define-config') module.exports = defineConfig({ env: { es6: true, browser: true, node: true }, plugins: ['@typescript-eslint', 'prettier', 'unicorn'], extends: [ 'eslint:recommended', 'plugin:import/recommended', 'plugin:eslint-comments/recommended', 'plugin:jsonc/recommended-with-jsonc', 'plugin:markdown/recommended', 'plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'prettier' ], settings: { 'import/resolver': { node: { extensions: ['.js', '.mjs', '.ts', '.d.ts', '.tsx'] } } }, overrides: [ { files: ['*.ts', '*.vue'], rules: { 'no-undef': 'off', '@typescript-eslint/ban-types': 'off' } }, { files: ['*.js'], rules: { '@typescript-eslint/no-var-requires': 'off' } }, { files: ['*.vue'], parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', extraFileExtensions: ['.vue'], ecmaVersion: 'latest', ecmaFeatures: { jsx: true } }, rules: { 'no-undef': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-empty-function': 'off' } } ], rules: { // js/ts camelcase: ['error', { properties: 'never' }], 'no-console': ['warn', { allow: ['error'] }], 'no-debugger': 'warn', 'no-constant-condition': ['error', { checkLoops: false }], 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'], 'no-return-await': 'error', 'no-var': 'error', 'no-empty': ['error', { allowEmptyCatch: true }], 'prefer-const': ['warn', { destructuring: 'all', ignoreReadBeforeAssign: true }], 'prefer-arrow-callback': ['error', { allowNamedFunctions: false, allowUnboundThis: true }], 'object-shorthand': ['error', 'always', { ignoreConstructors: false, avoidQuotes: true }], 'prefer-rest-params': 'error', 'prefer-spread': 'error', 'prefer-template': 'error', 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': 'error', // best-practice 'array-callback-return': 'error', 'block-scoped-var': 'error', 'no-alert': 'warn', 'no-case-declarations': 'error', 'no-multi-str': 'error', 'no-with': 'error', 'no-void': 'error', 'sort-imports': [ 'warn', { ignoreCase: false, ignoreDeclarationSort: true, ignoreMemberSort: false, memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], allowSeparatedGroups: false } ], // stylistic-issues 'prefer-exponentiation-operator': 'error', // ts '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', '@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }], '@typescript-eslint/ban-ts-comment': ['off', { 'ts-ignore': false }], '@typescript-eslint/no-empty-function': 'off', // vue 'vue/no-v-html': 'off', 'vue/require-default-prop': 'off', 'vue/require-explicit-emits': 'off', 'vue/multi-word-component-names': 'off', 'vue/prefer-import-from-vue': 'off', 'vue/no-v-text-v-html-on-component': 'off', 'vue/html-self-closing': [ 'error', { html: { void: 'always', normal: 'always', component: 'always' }, svg: 'always', math: 'always' } ], // prettier //fix lf error 'prettier/prettier': 'off', // import 'import/first': 'warn', 'import/no-duplicates': 'error', 'import/order': [ 'error', { groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], pathGroups: [ { pattern: 'vue', group: 'external', position: 'before' } ], pathGroupsExcludedImportTypes: ['type'] } ], 'import/no-unresolved': 'off', 'import/namespace': 'off', 'import/default': 'off', 'import/no-named-as-default': 'off', 'import/no-named-as-default-member': 'off', 'import/named': 'off', // eslint-plugin-eslint-comments 'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], // unicorn 'unicorn/custom-error-definition': 'error', 'unicorn/error-message': 'error', 'unicorn/escape-case': 'error', 'unicorn/import-index': 'error', 'unicorn/new-for-builtins': 'error', 'unicorn/no-array-method-this-argument': 'error', 'unicorn/no-array-push-push': 'error', 'unicorn/no-console-spaces': 'error', 'unicorn/no-for-loop': 'error', 'unicorn/no-hex-escape': 'error', 'unicorn/no-instanceof-array': 'error', 'unicorn/no-invalid-remove-event-listener': 'error', 'unicorn/no-new-array': 'error', 'unicorn/no-new-buffer': 'error', 'unicorn/no-unsafe-regex': 'off', 'unicorn/number-literal-case': 'error', 'unicorn/prefer-array-find': 'error', 'unicorn/prefer-array-flat-map': 'error', 'unicorn/prefer-array-index-of': 'error', 'unicorn/prefer-array-some': 'error', 'unicorn/prefer-date-now': 'error', 'unicorn/prefer-dom-node-dataset': 'error', 'unicorn/prefer-includes': 'error', 'unicorn/prefer-keyboard-event-key': 'error', 'unicorn/prefer-math-trunc': 'error', 'unicorn/prefer-modern-dom-apis': 'error', 'unicorn/prefer-negative-index': 'error', 'unicorn/prefer-number-properties': 'error', 'unicorn/prefer-optional-catch-binding': 'error', 'unicorn/prefer-prototype-methods': 'error', 'unicorn/prefer-query-selector': 'error', 'unicorn/prefer-reflect-apply': 'error', 'unicorn/prefer-string-slice': 'error', 'unicorn/prefer-string-starts-ends-with': 'error', 'unicorn/prefer-string-trim-start-end': 'error', 'unicorn/prefer-type-error': 'error', 'unicorn/throw-new-error': 'error' } }) ================================================ FILE: index.html ================================================ <%= title %>
================================================ FILE: mock/example.ts ================================================ export default [ { url: '/getMapInfo', method: 'get', response: () => { return { code: 200, title: 'mock请求测试' } } } ] ================================================ FILE: mock/user.ts ================================================ const user = { url: '/mock/login', method: 'post', response: () => { return { code: 20000, jwtToken:"666666" } } } const loginOut = { url: '/mock/loginOut', method: 'post', response: () => { return { code: 200, title: 'mock请求测试' } } } export default [ user,loginOut ] ================================================ FILE: optimize-include.ts ================================================ // const fs = require('fs') // const files = fs.readdirSync( // 'D:\\github\\vue3-admin-ts\\node_modules\\.pnpm\\element-plus@2.2.9_vue@3.2.37\\node_modules\\element-plus\\es\\components\\' // ) // console.log(111, JSON.stringify(files)) // console.log(console.dir(files)) // console.log(console.dir(files.slice(20))) import { resolve } from 'path' const elementPlusComponentNameArr = [ 'affix', 'alert', 'aside', 'autocomplete', 'avatar', 'backtop', 'badge', 'base', 'breadcrumb', 'breadcrumb-item', 'button', 'button-group', 'calendar', 'card', 'carousel', 'carousel-item', 'cascader', 'cascader-panel', 'check-tag', 'checkbox', 'checkbox-button', 'checkbox-group', 'col', 'collapse', 'collapse-item', 'collapse-transition', 'color-picker', 'config-provider', 'container', 'date-picker', 'descriptions', 'descriptions-item', 'dialog', 'divider', 'drawer', 'dropdown', 'dropdown-item', 'dropdown-menu', 'empty', 'footer', 'form', 'form-item', 'header', 'icon', 'image', 'image-viewer', 'infinite-scroll', 'input', 'input-number', 'link', 'loading', 'main', 'menu', 'menu-item', 'menu-item-group', 'message', 'message-box', 'notification', 'option', 'option-group', 'overlay', 'page-header', 'pagination', 'popconfirm', 'popover', 'popper', 'progress', 'radio', 'radio-button', 'radio-group', 'rate', 'result', 'row', 'scrollbar', 'select', 'select-v2', 'skeleton', 'skeleton-item', 'slider', 'space', 'step', 'steps', 'sub-menu', 'switch', 'tab-pane', 'table', 'table-column', 'table-v2', 'tabs', 'tag', 'teleport', 'time-picker', 'time-select', 'timeline', 'timeline-item', 'tooltip', 'transfer', 'tree', 'tree-select', 'tree-v2', 'upload', 'virtual-list' ] export const pkgPath = resolve(__dirname, './package.json') // eslint-disable-next-line @typescript-eslint/no-var-requires let { dependencies } = require(pkgPath) dependencies = Object.keys(dependencies).filter((dep) => !dep.startsWith('@types/')) const EPDepsArr = () => { const depsArr = [] as string[] elementPlusComponentNameArr.forEach((feItem) => { depsArr.push(`element-plus/es/components/${feItem}/style/index`) }) return depsArr } export const optimizeElementPlus = EPDepsArr() export const optimizeDependencies = dependencies export default [] ================================================ FILE: package.json ================================================ { "name": "vue3-admin-ts", "version": "2.2.0", "license": "MIT", "author": "kuanghua(869653722@qq.com)", "packageManager": "pnpm@7.9.0", "type": "module", "scripts": { "dev": "vite --mode serve-dev", "local": "vite --mode serve-local", "debug": "vite --mode serve-dev --debug", "test": "vite --mode serve-test", "build:test": "vite build --mode build-test", "build": "vite build --mode build", "preview:build": "npm run build && vite preview ", "preview": "vite preview ", "lint": "eslint --ext .js,.jsx,.vue,.ts,.tsx src --fix", "prepare": "husky install", "tsc-check": "tsc", "vitest": "vitest --ui", "coverage": "vitest run --coverage" }, "peerDependencies": { "vue": "^3.4.14" }, "dependencies": { "@element-plus/icons-vue": "^2.0.4", "axios": "1.6.5", "codemirror": "^6.0.1", "echarts": "5.3.2", "element-plus": "2.5.3", "js-error-collection": "^1.0.7", "json-editor-vue3": "^1.0.8", "mitt": "3.0.0", "moment-mini": "2.22.1", "nprogress": "0.2.0", "path": "0.12.7", "path-browserify": "^1.0.1", "path-to-regexp": "^6.2.1", "pinia": "^2.0.16", "pinia-plugin-persistedstate": "2.3.0", "screenfull": "^6.0.2", "sortablejs": "^1.15.0", "vue": "^3.4.14", "vue-clipboard3": "^2.0.0", "vue-codemirror": "^6.1.1", "vue-i18n": "9.1.10", "vue-router": "^4.1.5", "@codemirror/lang-javascript": "^6.1.0", "@codemirror/theme-one-dark": "^6.1.0" }, "devDependencies": { "@babel/eslint-parser": "7.16.3", "@originjs/vite-plugin-commonjs": "^1.0.3", "@types/mockjs": "1.0.10", "@types/node": "^17.0.35", "@types/path-browserify": "^1.0.0", "@types/sortablejs": "^1.15.0", "@typescript-eslint/eslint-plugin": "5.30.0", "@typescript-eslint/parser": "5.30.0", "@vitejs/plugin-legacy": "^5.2.0", "@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue-jsx": "^3.1.0", "@vitest/coverage-c8": "^0.33.0", "@vitest/ui": "^1.2.0", "@vue/cli-plugin-unit-jest": "4.5.17", "@vue/cli-service": "5.0.8", "@vue/test-utils": "^2.0.2", "@vueuse/core": "^8.7.5", "eslint": "8.18.0", "eslint-config-prettier": "8.5.0", "eslint-define-config": "1.5.1", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.26.0", "eslint-plugin-jsonc": "^2.3.0", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-prettier": "4.1.0", "eslint-plugin-simple-import-sort": "^10.0.0", "eslint-plugin-unicorn": "^43.0.2", "eslint-plugin-vue": "9.1.1", "husky": "7.0.2", "jsdom": "16.4.0", "jsonc-eslint-parser": "^2.1.0", "majestic": "1.8.1", "mockjs": "1.1.0", "prettier": "2.2.1", "resize-observer-polyfill": "^1.5.1", "rollup-plugin-visualizer": "^5.8.3", "sass": "1.77.6", "svg-sprite-loader": "6.0.11", "typescript": "^4.7.2", "unocss": "^0.58.3", "unplugin-auto-import": "^0.11.2", "unplugin-vue-components": "^0.22.8", "unplugin-vue-define-options": "^0.6.1", "vite": "^5.0.11", "vite-plugin-html": "^3.2.0", "vite-plugin-mkcert": "^1.7.2", "vite-plugin-mock": "^3.0.1", "vite-plugin-style-import": "1.2.1", "vite-plugin-svg-icons": "^2.0.1", "vitest": "^0.22.1", "vue-tsc": "^0.34.16" }, "pnpm": { "peerDependencyRules": { "ignoreMissing": [ "html-webpack-plugin", "vite-plugin-mock", "unplugin-auto-import", "unplugin-vue-components", "vue-template-compiler", "unocss", "unplugin", "vite-plugin-mock", "@vitejs/plugin-legacy", "@vitejs/plugin-vue", "@vitejs/*", "@babel/*", "vite", "vue", "@unocss/vite", "rollup", "vue-jest", "@babel/*" ] } }, "browserslist": [ "> 1%", "not ie 11", "not op_mini all" ], "engines": { "node": ">= 16 <20", "pnpm": ">= 6 <9" } } ================================================ FILE: src/App.vue ================================================ ================================================ FILE: src/api/user.ts ================================================ //获取用户信息 import axiosReq from 'axios' // export const userInfoReq = (): Promise => { // return new Promise((resolve) => { // const reqConfig = { // url: '/basis-func/user/getUserInfo', // params: { plateFormId: 2 }, // method: 'post' // } // axiosReq(reqConfig).then(({ data }) => { // resolve(data) // }) // }) // } //登录 export const loginReq = (subForm) => { return axiosReq({ url: '/mock/login', params: subForm, method: 'post' }) } //退出登录 export const loginOutReq = () => { return axiosReq({ url: '/mock/loginOut', method: 'post' }) } ================================================ FILE: src/components/ElSvgIcon.vue ================================================ ================================================ FILE: src/components/TestUnit.vue ================================================ ================================================ FILE: src/components/__tests__/el-svgIcon.test.jsx ================================================ import { markRaw, nextTick, ref } from 'vue' import { mount } from '@vue/test-utils' import { describe, expect, it, test } from 'vitest' import { Loading, Search } from '@element-plus/icons-vue' import ElSvgIcon from '../ElSvgIcon.vue' // const AXIOM = 'Rem is the best girl' describe('ElSvgIcon.vue', () => { it('create', () => { const wrapper = mount(() => ) // console.log(111111, wrapper.classes()) // expect(wrapper.classes()).toContain('el-icon') }) // it('icon', () => { // const wrapper = mount(() => ) // expect(wrapper.findAll('Search11222')).toBeTruthy() // }) // // it('size', () => { // const wrapper = mount(() => ) // expect(wrapper.findAll('20px')).toBeTruthy() // }) // // it('color', () => { // const wrapper = mount(() => ) // expect(wrapper.findAll('red')).toBeTruthy() // }) // // it('nativeType', () => { // const wrapper = mount(() => ) // // expect(wrapper.attributes('type')).toBe('submit') // }) // // it('loading', () => { // const wrapper = mount(() => ) // // expect(wrapper.classes()).toContain('is-loading') // expect(wrapper.findComponent(Loading).exists()).toBeTruthy() // }) // // it('size', () => { // const wrapper = mount(() => ) // // expect(wrapper.classes()).toContain('el-button--large') // }) // // it('plain', () => { // const wrapper = mount(() => ) // // expect(wrapper.classes()).toContain('is-plain') // }) // // it('round', () => { // const wrapper = mount(() => ) // expect(wrapper.classes()).toContain('is-round') // }) // // it('circle', () => { // const wrapper = mount(() => ) // // expect(wrapper.classes()).toContain('is-circle') // }) // it('text', async () => { // const bg = ref(false) // // const wrapper = mount(() => ) // // expect(wrapper.classes()).toContain('is-text') // // bg.value = true // // await nextTick() // // expect(wrapper.classes()).toContain('is-has-bg') // }) // it('link', async () => { // const wrapper = mount(() => ) // // expect(wrapper.classes()).toContain('is-link') // }) // test('render text', () => { // const wrapper = mount(() => ( // AXIOM // }} // /> // )) // // expect(wrapper.text()).toEqual(AXIOM) // }) // // test('handle click', async () => { // const wrapper = mount(() => ( // AXIOM // }} // /> // )) // // await wrapper.trigger('click') // expect(wrapper.emitted()).toBeDefined() // }) // // test('handle click inside', async () => { // const wrapper = mount(() => ( // // }} // /> // )) // // wrapper.find('.inner-slot').trigger('click') // expect(wrapper.emitted()).toBeDefined() // }) // // test('loading implies disabled', async () => { // const wrapper = mount(() => ( // AXIOM // }} // loading // /> // )) // // await wrapper.trigger('click') // expect(wrapper.emitted('click')).toBeUndefined() // }) // // it('disabled', async () => { // const wrapper = mount(() => ) // // expect(wrapper.classes()).toContain('is-disabled') // await wrapper.trigger('click') // expect(wrapper.emitted('click')).toBeUndefined() // }) // // it('loading icon', () => { // const wrapper = mount(() => ) // // expect(wrapper.findComponent(Search).exists()).toBeTruthy() // }) // // it('loading slot', () => { // const wrapper = mount({ // setup: () => () => ( // 111 }} loading={true}> // Loading // // ) // }) // // expect(wrapper.find('.custom-loading').exists()).toBeTruthy() // }) }) // describe('ElSvgIcon Group', () => { // it('create', () => { // const wrapper = mount({ // setup: () => () => // ( // // Prev // Next // // ) // }) // expect(wrapper.classes()).toContain('el-button-group') // expect(wrapper.findAll('button').length).toBe(2) // }) // // it('button group reactive size', async () => { // const size = ref('small') // const wrapper = mount({ // setup: () => () => // ( // // Prev // Next // // ) // }) // expect(wrapper.classes()).toContain('el-button-group') // expect(wrapper.findAll('.el-button-group button.el-button--small').length).toBe(2) // // size.value = 'large' // await nextTick() // // expect(wrapper.findAll('.el-button-group button.el-button--large').length).toBe(2) // }) // // it('button group type', async () => { // const wrapper = mount({ // setup: () => () => // ( // // Prev // Next // // ) // }) // expect(wrapper.classes()).toContain('el-button-group') // expect(wrapper.findAll('.el-button-group button.el-button--primary').length).toBe(1) // expect(wrapper.findAll('.el-button-group button.el-button--warning').length).toBe(1) // }) // // it('add space in two Chinese characters', async () => { // const wrapper = mount(() => ( // '中文' // }} // autoInsertSpace // /> // )) // // expect(wrapper.find('.el-button span').text()).toBe('中文') // expect(wrapper.find('.el-button span').classes()).toContain('el-button__text--expand') // }) // // it('add space between two Chinese characters even if there is whitespace at both ends', async () => { // const wrapper = mount(() =>  中文 ) // // expect(wrapper.find('.el-button span').text()).toBe('中文') // expect(wrapper.find('.el-button span').classes()).toContain('el-button__text--expand') // }) // }) ================================================ FILE: src/directives/button-codes.ts ================================================ import { useBasicStore } from '@/store/basic' function checkPermission(el, { value }) { if (value && Array.isArray(value)) { if (value.length) { const permissionRoles = value const hasPermission = useBasicStore().buttonCodes?.some((code) => permissionRoles.includes(code)) if (!hasPermission) el.parentNode && el.parentNode.removeChild(el) } } else { throw new Error(`need roles! Like v-permission="['admin','editor']"`) } } export default { mounted(el, binding) { checkPermission(el, binding) }, componentUpdated(el, binding) { checkPermission(el, binding) } } ================================================ FILE: src/directives/codes-permission.ts ================================================ import { useBasicStore } from '@/store/basic' function checkPermission(el, { value }) { if (value && Array.isArray(value)) { if (value.length > 0) { const permissionRoles = value const hasPermission = useBasicStore().codes?.some((role) => permissionRoles.includes(role)) if (!hasPermission) el.parentNode && el.parentNode.removeChild(el) } } else { throw new Error(`need codes! Like v-codes-permission="['admin','editor']"`) } } export default { mounted(el, binding) { checkPermission(el, binding) }, componentUpdated(el, binding) { checkPermission(el, binding) } } ================================================ FILE: src/directives/index.ts ================================================ import buttonCodes from './button-codes' import codesPermission from './codes-permission' import rolesPermission from './roles-permission' import lang from './lang' export default function (app) { app.directive('ButtonCodes', buttonCodes) app.directive('CodesPermission', codesPermission) app.directive('RolesPermission', rolesPermission) app.directive('lang', lang) } ================================================ FILE: src/directives/lang.ts ================================================ import { watch } from 'vue' import { storeToRefs } from 'pinia/dist/pinia' import { langTitle } from '@/hooks/use-common' import { useConfigStore } from '@/store/config' //element-plus const componentToProps = { ElInput: 'placeholder', ElTableColumn: 'label' } function checkPermission(el, { value }) { let saveOriginTitle = '' const { language } = storeToRefs(useConfigStore()) //save the original title const name = el.__vueParentComponent?.type?.name const nameTitle = el.__vueParentComponent?.props[componentToProps[name]] saveOriginTitle = nameTitle || el.innerText watch( () => language.value, () => { //element tag or component if (name?.startsWith('EL')) { //self cunstrom if (Object.keys(componentToProps).includes(name)) { const props = el.__vueParentComponent.props props[componentToProps[name]] = langTitle(saveOriginTitle) } else { el.innerText = langTitle(saveOriginTitle) } } else { //common tag such as div span output so on; if (el.__vnode?.type) { el.innerText = langTitle(saveOriginTitle) } } }, { immediate: true } ) } export default { mounted(el, binding) { checkPermission(el, binding) } } ================================================ FILE: src/directives/roles-permission.ts ================================================ import { useBasicStore } from '@/store/basic' function checkPermission(el, { value }) { if (value && Array.isArray(value)) { if (value.length > 0) { const permissionRoles = value const hasPermission = useBasicStore().roles?.some((role) => permissionRoles.includes(role)) if (!hasPermission) el.parentNode && el.parentNode.removeChild(el) } } else { throw new Error(`need roles! Like v-roles-permission="['admin','editor']"`) } } export default { mounted(el, binding) { checkPermission(el, binding) }, componentUpdated(el, binding) { checkPermission(el, binding) } } ================================================ FILE: src/hooks/use-common.ts ================================================ //复制文本 import useClipboard from 'vue-clipboard3' import { ElMessage } from 'element-plus' // i18n language match title import { i18n } from '@/lang' // the keys using zh file import langEn from '@/lang/zh' import settings from '@/settings' export const sleepTimeout = (time: number) => { return new Promise((resolve) => { const timer = setTimeout(() => { clearTimeout(timer) resolve(null) }, time) }) } //深拷贝 export function cloneDeep(value) { return JSON.parse(JSON.stringify(value)) } //copyValueToClipboard const { toClipboard } = useClipboard() export const copyValueToClipboard = (value: any) => { toClipboard(JSON.stringify(value)) ElMessage.success('复制成功') } const { t, te } = i18n.global export const langTitle = (title) => { if (!title) { return settings.title } for (const key of Object.keys(langEn)) { if (te(`${key}.${title}`) && t(`${key}.${title}`)) { return t(`${key}.${title}`) } } return title } //get i18n instance export const getLangInstance = () => { return i18n.global as ObjKeys } ================================================ FILE: src/hooks/use-element.ts ================================================ import { reactive, ref, toRefs } from 'vue' import { ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-plus' import type { EpPropMergeType } from 'element-plus/es/utils' export const useElement = () => { // 正整数 const upZeroInt = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)) } if (/^\+?[1-9]\d*$/.test(value)) { callback() } else { callback(new Error(`${msg}输入有误`)) } } // 正整数(包括0) const zeroInt = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)) } if (/^\+?[0-9]\d*$/.test(value)) { callback() } else { callback(new Error(`${msg}输入有误`)) } } // 金额 const money = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)) } if (/((^[1-9]\d*)|^0)(\.\d{0,2}){0,1}$/.test(value)) { callback() } else { callback(new Error(`${msg}输入有误`)) } } // 手机号 const phone = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)) } if (/^0?1[0-9]{10}$/.test(value)) { callback() } else { callback(new Error(`${msg}输入有误`)) } } // 邮箱 const email = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)) } if (/(^([a-zA-Z]|[0-9])(\w|-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4}))$/.test(value)) { callback() } else { callback(new Error(`${msg}`)) } } const state = reactive({ /* table*/ tableData: [], rowDeleteIdArr: [], loadingId: null, /* 表单*/ formModel: {}, subForm: {}, searchForm: {}, /* 表单校验*/ formRules: { //非空 isNull: (msg: string) => [{ required: false, message: `${msg}`, trigger: 'blur' }], isNotNull: (msg: string) => [{ required: true, message: `${msg}`, trigger: 'blur' }], // 正整数 upZeroInt: (msg: string) => [ { required: true, validator: (rule, value, callback) => upZeroInt(rule, value, callback, msg), trigger: 'blur' } ], // 正整数(包括0) zeroInt: (msg: string) => [ { required: true, validator: (rule, value, callback) => zeroInt(rule, value, callback, msg), trigger: 'blur' } ], // 金额 money: (msg: string) => [ { required: true, validator: (rule, value, callback) => money(rule, value, callback, msg), trigger: 'blur' } ], // 手机号 phone: (msg: string) => [ { required: true, validator: (rule, value, callback) => phone(rule, value, callback, msg), trigger: 'blur' } ], // 邮箱 email: (msg: string) => [ { required: true, validator: (rule, value, callback) => email(rule, value, callback, msg), trigger: 'blur' } ] }, /* 时间packing相关*/ datePickerOptions: { //选择今天以后的日期,包括今天 disabledDate: (time: any) => { return time.getTime() < Date.now() - 86400000 } }, startEndArr: [], /* dialog相关*/ dialogTitle: '添加', detailDialog: false, isDialogEdit: false, dialogVisible: false, tableLoading: false, /* 树相关*/ treeData: [], defaultProps: { children: 'children', label: 'label' } }) return { ...toRefs(state) } } /* * 通知弹框 * message:通知的内容 * type:通知类型 * duration:通知显示时长(ms) * */ export const elMessage = (message: string, type?) => { ElMessage({ showClose: true, message: message || '成功', type: type || ('success' as string), center: false }) } /* * loading加载框 * 调用后通过 loadingId.close() 进行关闭 * */ let loadingId: any = null export const elLoading = (msg?: string) => { loadingId = ElLoading.service({ lock: true, text: msg || '数据载入中', // spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.1)' }) } export const closeElLoading = () => { loadingId.close() } /* * 提示 * message: 提示内容 * type:提示类型 * title:提示标题 * duration:提示时长(ms) * */ export const elNotify = ( message: string, type: EpPropMergeType | undefined, title: string, duration: number ) => { ElNotification({ title: title || '提示', type: type || 'success', message: message || '请传入提示消息', position: 'top-right', duration: duration || 2500, offset: 40 }) } /* 确认弹框(没有取消按钮) * title:提示的标题 * message:提示的内容 * return Promise * */ export const elConfirmNoCancelBtn = (title: string, message: string) => { return ElMessageBox({ message: message || '你确定要删除吗', title: title || '确认框', confirmButtonText: '确定', cancelButtonText: '取消', showCancelButton: false, type: 'warning' }) } /* * 确认弹框 * title:提示的标题 * message:提示的内容 * return Promise * */ export const elConfirm = (title: string, message: string) => { return ElMessageBox({ message: message || '你确定要删除吗', title: title || '确认框', confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) } /* 级联*/ const cascaderKey = ref() export const casHandleChange = () => { // 解决目前级联选择器搜索输入报错问题 cascaderKey.value += cascaderKey.value } ================================================ FILE: src/hooks/use-error-log.ts ================================================ /*js 错误日志收集*/ import { jsErrorCollection } from 'js-error-collection' import axiosReq from 'axios' import pack from '../../package.json' import settings from '@/settings' import bus from '@/utils/bus' //此处不要使用utils下的axios const reqUrl = '/integration-front/errorCollection/insert' let repeatErrorLogJudge = '' const errorLogReq = (errLog: string) => { axiosReq({ url: import.meta.env.VITE_APP_BASE_URL+reqUrl, data: { pageUrl: window.location.href, errorLog: errLog, browserType: navigator.userAgent, version: pack.version }, method: 'post' }).then(() => { //通知错误列表页面更新数据 bus.emit('reloadErrorPage', {}) }) } export const useErrorLog = () => { //判断该环境是否需要收集错误日志,由settings配置决定 if (settings.errorLog?.includes(import.meta.env.VITE_APP_ENV)) { jsErrorCollection({ runtimeError: true, rejectError: true, consoleError: true }, (errLog) => { if (!repeatErrorLogJudge || !errLog.includes(repeatErrorLogJudge)) { errorLogReq(errLog) //移除重复日志,fix重复提交错误日志,避免造成死循环 repeatErrorLogJudge = errLog.slice(0, 20) } }) } } ================================================ FILE: src/hooks/use-layout.ts ================================================ /** * 判断是否是外链 * @param {string} path * @returns {Boolean} */ import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue' import { useBasicStore } from '@/store/basic' export function isExternal(path) { return /^(https?:|mailto:|tel:)/.test(path) } /*判断窗口变化控制侧边栏收起或展开*/ export function resizeHandler() { const { body } = document const WIDTH = 992 const basicStore = useBasicStore() const isMobile = () => { const rect = body.getBoundingClientRect() return rect.width - 1 < WIDTH } const resizeHandler = () => { if (!document.hidden) { if (isMobile()) { /*此处只做根据window尺寸关闭sideBar功能*/ basicStore.setSidebarOpen(false) } else { basicStore.setSidebarOpen(true) } } } onBeforeMount(() => { window.addEventListener('resize', resizeHandler) }) onMounted(() => { if (isMobile()) { basicStore.setSidebarOpen(false) } else { basicStore.setSidebarOpen(true) } }) onBeforeUnmount(() => { window.removeEventListener('resize', resizeHandler) }) } ================================================ FILE: src/hooks/use-permission.ts ================================================ import NProgress from 'nprogress' import type { RouteRawConfig, RouterTypes, rawConfig } from '~/basic' import type { RouteRecordName } from 'vue-router' /** * 根据请求,过滤异步路由 * @param:menuList 异步路由数组 * return 过滤后的异步路由 */ // @ts-ignore import Layout from '@/layout/index.vue' /* * 路由操作 * */ import router, { asyncRoutes, constantRoutes, roleCodeRoutes } from '@/router' //进度条 import 'nprogress/nprogress.css' import { useBasicStore } from '@/store/basic' const buttonCodes: Array = [] //按钮权限 interface menuRow { category: number code: number children: RouterTypes } export const filterAsyncRoutesByMenuList = (menuList) => { const filterRouter: RouterTypes = [] menuList.forEach((route: menuRow) => { //button permission if (route.category === 3) { buttonCodes.push(route.code) } else { //generator every router item by menuList const itemFromReqRouter = getRouteItemFromReqRouter(route) if (route.children?.length) { //judge the type is router or button itemFromReqRouter.children = filterAsyncRoutesByMenuList(route.children) } filterRouter.push(itemFromReqRouter) } }) return filterRouter } const getRouteItemFromReqRouter = (route): RouteRawConfig => { const tmp: rawConfig = { meta: { title: '' } } const routeKeyArr = ['path', 'component', 'redirect', 'alwaysShow', 'name', 'hidden'] const metaKeyArr = ['title', 'activeMenu', 'elSvgIcon', 'icon'] // @ts-ignore const modules = import.meta.glob('../views/**/**.vue') //generator routeKey routeKeyArr.forEach((fItem) => { if (fItem === 'component') { if (route[fItem] === 'Layout') { tmp[fItem] = Layout } else { //has error , i will fix it through plugins //tmp[fItem] = () => import(`@/views/permission-center/test/TestTableQuery.vue`) tmp[fItem] = modules[`../views/${route[fItem]}`] } } else if (fItem === 'path' && route.parentId === 0) { tmp[fItem] = `/${route[fItem]}` } else if (['hidden', 'alwaysShow'].includes(fItem)) { tmp[fItem] = !!route[fItem] } else if (['name'].includes(fItem)) { tmp[fItem] = route['code'] } else if (route[fItem]) { tmp[fItem] = route[fItem] } }) //generator metaKey metaKeyArr.forEach((fItem) => { if (route[fItem] && tmp.meta) tmp.meta[fItem] = route[fItem] }) //route extra insert if (route.extra) { Object.entries(route.extra.parse(route.extra)).forEach(([key, value]) => { if (key === 'meta' && tmp.meta) { tmp.meta[key] = value } else { tmp[key] = value } }) } return tmp as RouteRawConfig } /** * 根据角色数组过滤异步路由 * @param routes asyncRoutes 未过滤的异步路由 * @param roles 角色数组 * return 过滤后的异步路由 */ export function filterAsyncRoutesByRoles(routes, roles) { const res: RouterTypes = [] routes.forEach((route) => { const tmp: RouteRawConfig = { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutesByRoles(tmp.children, roles) } res.push(tmp) } }) return res } function hasPermission(roles, route) { if (route?.meta?.roles) { return roles?.some((role) => route.meta.roles.includes(role)) } else { return true } } /** * 根据code数组,过滤异步路由 * @param codes code数组 * @param codesRoutes 未过滤的异步路由 * return 过滤后的异步路由 */ export function filterAsyncRouterByCodes(codesRoutes, codes) { const filterRouter: RouterTypes = [] codesRoutes.forEach((routeItem: RouteRawConfig) => { if (hasCodePermission(codes, routeItem)) { if (routeItem.children) routeItem.children = filterAsyncRouterByCodes(routeItem.children, codes) filterRouter.push(routeItem) } }) return filterRouter } function hasCodePermission(codes, routeItem) { if (routeItem.meta?.code) { return codes.includes(routeItem.meta.code) || routeItem.hidden } else { return true } } //过滤异步路由 export function filterAsyncRouter({ menuList, roles, codes }) { const basicStore = useBasicStore() let accessRoutes: RouterTypes = [] const permissionMode = basicStore.settings?.permissionMode if (permissionMode === 'rbac') { accessRoutes = filterAsyncRoutesByMenuList(menuList) //by menuList } else if (permissionMode === 'roles') { accessRoutes = filterAsyncRoutesByRoles(roleCodeRoutes, roles) //by roles } else { accessRoutes = filterAsyncRouterByCodes(roleCodeRoutes, codes) //by codes } accessRoutes.forEach((route) => router.addRoute(route)) asyncRoutes.forEach((item) => router.addRoute(item)) basicStore.setFilterAsyncRoutes(accessRoutes) } //重置路由 export function resetRouter() { //移除之前存在的路由 const routeNameSet: Set = new Set() router.getRoutes().forEach((fItem) => { if (fItem.name) routeNameSet.add(fItem.name) }) routeNameSet.forEach((setItem) => router.removeRoute(setItem)) //新增constantRoutes constantRoutes.forEach((feItem) => router.addRoute(feItem)) } //重置登录状态 export function resetState() { resetRouter() useBasicStore().resetState() } //刷新路由 export function freshRouter(data) { resetRouter() filterAsyncRouter(data) // location.reload() } NProgress.configure({ showSpinner: false }) //开始进度条 export const progressStart = () => { NProgress.start() } //关闭进度条 export const progressClose = () => { NProgress.done() } ================================================ FILE: src/hooks/use-self-router.ts ================================================ import router from '@/router' export const getQueryParam = () => { const route: any = router.currentRoute if (route.value?.query.params) { return JSON.parse(route.value.query.params) } } // vue router export const routerPush = (name, params) => { let data = {} if (params) { data = { params: JSON.stringify(params) } } else { data = {} } router.push({ name, query: data }) } export const routerReplace = (name, params) => { let data = {} if (params) { data = { params: JSON.stringify(params) } } else { data = {} } router.replace({ name, query: data }) } export const routeInfo = () => { return router.currentRoute } export const routerBack = () => { router.go(-1) } ================================================ FILE: src/hooks/use-table.ts ================================================ import { ref } from 'vue' import momentMini from 'moment-mini' import { elConfirm, elMessage } from './use-element' export const useTable = (searchForm, selectPageReq) => { /*define ref*/ const tableListData = ref([]) const totalPage = ref(0) const pageNum = ref(1) const pageSize = ref(20) //列表请求 const tableListReq = (config) => { const data = Object.assign( { pageNum: pageNum.value, pageSize: pageSize.value }, JSON.parse(JSON.stringify(searchForm)) ) Object.keys(data).forEach((fItem) => { if (['', null, undefined, Number.NaN].includes(data[fItem])) delete data[fItem] if (config.method === 'get') { if (Array.isArray(data[fItem])) delete data[fItem] if (data[fItem] instanceof Object) delete data[fItem] } }) const reqConfig = { data, ...config } return axiosReq(reqConfig) } /** * 日期范围选择处理 * @param timeArr choose the time * @author 熊猫哥 * @date 2022/9/25 14:02 */ const dateRangePacking = (timeArr) => { if (timeArr && timeArr.length === 2) { searchForm.startTime = timeArr[0] //取今天23点 if (searchForm.endTime) { searchForm.endTime = momentMini(timeArr[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss') } } else { searchForm.startTime = '' searchForm.endTime = '' } } //当前页 const handleCurrentChange = (val) => { pageNum.value = val selectPageReq() } const handleSizeChange = (val) => { pageSize.value = val selectPageReq() } const resetPageReq = () => { pageNum.value = 1 selectPageReq() } /*多选*/ const multipleSelection = ref>([]) const handleSelectionChange = (val) => { multipleSelection.value = val } /*批量删除*/ const multiDelBtnDill = (reqConfig) => { let rowDeleteIdArr: Array = [] let deleteNameTitle = '' rowDeleteIdArr = multipleSelection.value.map((mItem) => { deleteNameTitle = `${deleteNameTitle + mItem.id},` return mItem.id }) if (rowDeleteIdArr.length === 0) { elMessage('表格选项不能为空', 'warning') return } const stringLength = deleteNameTitle.length - 1 elConfirm('删除', `您确定要删除【${deleteNameTitle.slice(0, stringLength)}】吗`).then(() => { const data = rowDeleteIdArr axiosReq({ data, method: 'DELETE', bfLoading: true, ...reqConfig }).then(() => { elMessage('删除成功') resetPageReq() }) }) } //单个删除 const tableDelDill = (row, reqConfig) => { elConfirm('确定', `您确定要删除【${row.id}】吗?`).then(() => { axiosReq(reqConfig).then(() => { resetPageReq() elMessage(`【${row.id}】删除成功`) }) }) } return { pageNum, pageSize, totalPage, tableListData, tableListReq, dateRangePacking, multipleSelection, handleSelectionChange, handleCurrentChange, handleSizeChange, resetPageReq, multiDelBtnDill, tableDelDill } } ================================================ FILE: src/icons/SvgIcon.vue ================================================ ================================================ FILE: src/lang/en.ts ================================================ export default { router: { Dashboard: '', 'Setting Switch': '', 'Error Log': '', 'Error Index': '', 'Error Generator': '', Nested: '', Menu1: '', 'Menu1-1': '', 'Menu1-2': '', 'Menu1-2-1': '', 'Menu1-2-2': '', 'Menu1-3': '', menu2: '', 'External Link': '', 'Basic Demo': '', Hook: '', Pinia: '', Mock: '', 'Svg Icon': '', 'Parent Children': '', 'KeepAlive Group': '', 'Tab KeepAlive': '', 'Third KeepAlive': '', SecondChildren: '', ThirdChildren: '', Worker: '', Permission: '', 'Permission Switch': '', 'Role Index': '', 'Code Index': '', 'Button Permission': '' }, navbar: { Home: '', Github: '', Docs: '', 'login out': '' }, //page dashboard: { 'switch theme': '', 'switch size': '', 'switch language': '', en: 'English', zh: '中文', 'Button Group': '', 'unocss using': '', 'global var': '' }, 'error-log': { log: '', pageUrl: '', startDate: '', endDate: '', github: '', search: '', reset: '', multiDel: '' }, permission: { addRole: '', editPermission: '', roles: '', switchRoles: '', tips: '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 el-tab 或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。', delete: '删除', confirm: '确定', cancel: '取消' }, guide: { description: '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。本 Demo 是基于', button: '打开引导' }, components: { documentation: '文档', tinymceTips: '富文本是管理后台一个核心的功能,但同时又是一个有很多坑的地方。在选择富文本的过程中我也走了不少的弯路,市面上常见的富文本都基本用过了,最终权衡了一下选择了Tinymce。更详细的富文本比较和介绍见', dropzoneTips: '由于我司业务有特殊需求,而且要传七牛 所以没用第三方,选择了自己封装。代码非常的简单,具体代码你可以在这里看到 @/components/Dropzone', stickyTips: '当页面滚动到预设的位置会吸附在顶部', backToTopTips1: '页面滚动到指定位置会在右下角出现返回顶部按钮', backToTopTips2: '可自定义按钮的样式、show/hide、出现的高度、返回的位置 如需文字提示,可在外部使用Element的el-tooltip元素', imageUploadTips: '由于我在使用时它只有vue@1版本,而且和mockjs不兼容,所以自己改造了一下,如果大家要使用的话,优先还是使用官方版本。' }, table: { dynamicTips1: '固定表头, 按照表头顺序排序', dynamicTips2: '不固定表头, 按照点击顺序排序', dragTips1: '默认顺序', dragTips2: '拖拽后顺序', title: '标题', importance: '重要性', type: '类型', remark: '点评', search: '搜索', add: '添加', export: '导出', reviewer: '审核人', id: '序号', date: '时间', author: '作者', readings: '阅读数', status: '状态', actions: '操作', edit: '编辑', publish: '发布', draft: '草稿', delete: '删除', cancel: '取 消', confirm: '确 定' } } ================================================ FILE: src/lang/index.ts ================================================ import { createI18n } from 'vue-i18n' import en from './en' import zh from './zh' import settings from '@/settings' const messages = { en, zh } const localeData = { globalInjection: true, //如果设置true, $t() 函数将注册到全局 legacy: false, //如果想在composition api中使用需要设置为false locale: settings.defaultLanguage, messages // set locale messages } export const i18n = createI18n(localeData) export const setupI18n = { install(app) { app.use(i18n) } } ================================================ FILE: src/lang/zh.ts ================================================ export default { router: { Dashboard: '首页', LowCodePlatFrom: '低代码平台', RBAC: '用户权限角色', 'Setting Switch': '配置文件', 'Error Log': '错误日志', 'Error Index': '错误日志列表', 'Error Generator': '错误日志生成', Nested: '路由嵌套', Menu1: '菜单1', 'Menu1-1': '菜单 1-1', 'Menu1-2': '菜单 1-2', 'Menu1-2-1': '菜单 1-2-1', 'Menu1-2-2': '菜单 1-2-2', 'Menu1-3': '菜单 1-3', menu2: '菜单 2', 'External Link': '外链', 'Basic Demo': '基础例子', Hook: 'hook', Pinia: 'pinia', Mock: 'mock', 'Svg Icon': 'svg使用', 'Parent Children': '父子组件通信', 'KeepAlive Group': '缓存组', 'Tab KeepAlive': 'tab缓存', 'Third KeepAlive': '三级路由缓存', SecondChildren: '三级路由示例1', ThirdChildren: '三级路由示例2', Worker: '多线程', Permission: '权限路由', 'Permission Switch': '权限切换', 'Role Index': '角色权限', 'Code Index': 'Code权限', 'Button Permission': '按钮权限', Charts: '图表', Excel: 'Excel', 'Rich Text': '富文本', Table: '表格', Guid: '使用引导', Other: '其他' }, tagsView: { Refresh: '刷新', Close: '关闭当前', 'Close Others': '关闭其他', 'Close All': '关闭所有' }, navbar: { Home: '首页', Github: '项目git地址', Docs: '官方文档', 'login out': '退出登录' }, //page dashboard: { 'switch theme': '切换主题色', 'switch size': '切换尺寸', 'switch language': '切换语言', en: 'English', zh: '中文', 'Button Group': '按钮组', 'unocss using': 'unocss使用', 'global var': '全局静态变量' }, 'error-log': { log: '错误日志', pageUrl: '页面路径', startDate: '开始日期', endDate: '结束日期', github: 'Github 地址', search: '查询', reset: '重置', multiDel: '批量删除' } // permission: { // addRole: '新增角色', // editPermission: '编辑权限', // roles: '你的权限', // switchRoles: '切换权限', // tips: // '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 el-tab 或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。', // delete: '删除', // confirm: '确定', // cancel: '取消' // }, // guide: { // description: '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。本 Demo 是基于', // button: '打开引导' // }, // components: { // documentation: '文档', // tinymceTips: // '富文本是管理后台一个核心的功能,但同时又是一个有很多坑的地方。在选择富文本的过程中我也走了不少的弯路,市面上常见的富文本都基本用过了,最终权衡了一下选择了Tinymce。更详细的富文本比较和介绍见', // dropzoneTips: // '由于我司业务有特殊需求,而且要传七牛 所以没用第三方,选择了自己封装。代码非常的简单,具体代码你可以在这里看到 @/components/Dropzone', // stickyTips: '当页面滚动到预设的位置会吸附在顶部', // backToTopTips1: '页面滚动到指定位置会在右下角出现返回顶部按钮', // backToTopTips2: // '可自定义按钮的样式、show/hide、出现的高度、返回的位置 如需文字提示,可在外部使用Element的el-tooltip元素', // imageUploadTips: // '由于我在使用时它只有vue@1版本,而且和mockjs不兼容,所以自己改造了一下,如果大家要使用的话,优先还是使用官方版本。' // }, // table: { // dynamicTips1: '固定表头, 按照表头顺序排序', // dynamicTips2: '不固定表头, 按照点击顺序排序', // dragTips1: '默认顺序', // dragTips2: '拖拽后顺序', // title: '标题', // importance: '重要性', // type: '类型', // remark: '点评', // search: '搜索', // add: '添加', // export: '导出', // reviewer: '审核人', // id: '序号', // date: '时间', // author: '作者', // readings: '阅读数', // status: '状态', // actions: '操作', // edit: '编辑', // publish: '发布', // draft: '草稿', // delete: '删除', // cancel: '取 消', // confirm: '确 定' // }, // example: { // warning: // '创建和编辑页面是不能被 keep-alive 缓存的,因为keep-alive 的 include 目前不支持根据路由来缓存,所以目前都是基于 component name 来进行缓存的。如果你想类似的实现缓存效果,可以使用 localStorage 等浏览器缓存方案。或者不要使用 keep-alive 的 include,直接缓存所有页面。详情见' // }, // errorLog: { // tips: '请点击右上角bug小图标', // description: // '现在的管理后台基本都是spa的形式了,它增强了用户体验,但同时也会增加页面出问题的可能性,可能一个小小的疏忽就导致整个页面的死锁。好在 Vue 官网提供了一个方法来捕获处理异常,你可以在其中进行错误处理或者异常上报。', // documentation: '文档介绍' // }, // excel: { // export: '导出', // selectedExport: '导出已选择项', // placeholder: '请输入文件名(默认excel-list)' // }, // zip: { // export: '导出', // placeholder: '请输入文件名(默认file)' // }, // pdf: { // tips: '这里使用 window.print() 来实现下载pdf的功能' // }, // theme: { // change: '换肤', // documentation: '换肤文档', // tips: 'Tips: 它区别于 navbar 上的 theme-pick, 是两种不同的换肤方法,各自有不同的应用场景,具体请参考文档。' // }, // tagsView: { // refresh: '刷新', // close: '关闭', // closeOthers: '关闭其它', // closeAll: '关闭所有' // }, // settings: { // title: '系统布局配置', // theme: '主题色', // tagsView: '开启 Tags-View', // fixedHeader: '固定 Header', // sidebarLogo: '侧边栏 Logo' // } } ================================================ FILE: src/layout/app-main/Breadcrumb.vue ================================================ ================================================ FILE: src/layout/app-main/Hamburger.vue ================================================ ================================================ FILE: src/layout/app-main/Navbar.vue ================================================ ================================================ FILE: src/layout/app-main/TagsView.vue ================================================ ================================================ FILE: src/layout/app-main/component/LangSelect.vue ================================================ ================================================ FILE: src/layout/app-main/component/ScreenFull.vue ================================================ ================================================ FILE: src/layout/app-main/component/ScreenLock.vue ================================================ ================================================ FILE: src/layout/app-main/component/SizeSelect.vue ================================================ ================================================ FILE: src/layout/app-main/component/ThemeSelect.vue ================================================ ================================================ FILE: src/layout/app-main/index.vue ================================================ ================================================ FILE: src/layout/index.vue ================================================ ================================================ FILE: src/layout/sidebar/Link.vue ================================================ ================================================ FILE: src/layout/sidebar/Logo.vue ================================================ ================================================ FILE: src/layout/sidebar/MenuIcon.vue ================================================ ================================================ FILE: src/layout/sidebar/SidebarItem.vue ================================================ ================================================ FILE: src/layout/sidebar/index.vue ================================================ ================================================ FILE: src/lib/el-svg-icon.ts ================================================ import * as components from '@element-plus/icons-vue' export default { install: (app) => { for (const key in components) { const componentConfig = components[key]; app.component(componentConfig.name, componentConfig); } }, }; ================================================ FILE: src/lib/element-plus.ts ================================================ import * as AllComponent from 'element-plus' //element-plus中按需引入会引起首次加载过慢 const elementPlusComponentNameArr = ['ElButton'] export default function (app) { elementPlusComponentNameArr.forEach((component) => { app.component(component, AllComponent[component]) }) } ================================================ FILE: src/main.ts ================================================ import { createApp } from 'vue' import { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import ElementPlus from 'element-plus' import App from './App.vue' import router from './router' //import theme import './theme/index.scss' //import unocss import 'uno.css' //i18n import { setupI18n } from '@/lang' import '@/styles/index.scss' // global css //svg-icon import 'virtual:svg-icons-register' import svgIcon from '@/icons/SvgIcon.vue' import directive from '@/directives' //import router intercept import './permission' //import element-plus import 'element-plus/dist/index.css' //import element-plus svg icon import ElSvgIcon from "@/lib/el-svg-icon" const app = createApp(App) app.use(ElSvgIcon) //router app.use(router) //pinia const pinia = createPinia() pinia.use(piniaPluginPersistedstate) app.use(pinia) //i18n app.use(setupI18n) app.component('SvgIcon', svgIcon) directive(app) //element-plus app.use(ElementPlus) app.mount('#app') ================================================ FILE: src/mock-prod-server.ts ================================================ // vite-plugin-mock v3.0.1 版本 生产mock有问题 // import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' // //https://cn.vitejs.dev/guide/features.html#glob-import // // @ts-ignore // const modulesFiles = import.meta.glob('../mock/*', { eager: true }) // let modules = [] // for (const filePath in modulesFiles) { // //读取文件内容到 modules // modules = modules.concat(modulesFiles[filePath].default) // } // export function setupProdMockServer() { // //创建prod mock server // createProdMockServer([...modules]) // } ================================================ FILE: src/permission.ts ================================================ import router from '@/router' import {progressClose, progressStart } from '@/hooks/use-permission' import { useBasicStore } from '@/store/basic' import { langTitle } from '@/hooks/use-common' import settings from "@/settings"; //路由进入前拦截 //to:将要进入的页面 vue-router4.0 不推荐使用next() const whiteList = ['/login', '/404', '/401'] // no redirect whitelist router.beforeEach(async (to) => { progressStart() document.title = langTitle(to.meta?.title) // i18 page title const basicStore = useBasicStore() //not login if (!settings.isNeedLogin) { basicStore.setFilterAsyncRoutes([]) return true } //1.判断token if (basicStore.token) { if (to.path === '/login') { return '/' } else { basicStore.setFilterAsyncRoutes([]) return true } } else { if (!whiteList.includes(to.path)) { return `/login?redirect=${to.path}` } else { return true } } }) //路由进入后拦截 router.afterEach(() => { progressClose() }) ================================================ FILE: src/plugins/vite-plugin-setup-extend/index.ts ================================================ import { parse } from '@vue/compiler-sfc' import { render } from 'ejs' import type { Plugin } from 'vite' export default ({ inject }): Plugin => { return { name: 'vite-plugin-setup-extend', enforce: 'pre', // configResolved(resolvedConfig) { // viteConfig = resolvedConfig // }, async transformIndexHtml(html) { const result = await render(html, { ...inject }) return result }, transform(code, id) { if (/\.vue$/.test(id)) { const { descriptor } = parse(code) if (!descriptor?.scriptSetup?.setup) { return null } const { lang, name } = descriptor.scriptSetup?.attrs || {} const dillStr = headString(lang, name) code += dillStr return code } } } } const headString = (lang, name) => { return `\n` } ================================================ FILE: src/router/index.ts ================================================ import { createRouter, createWebHashHistory } from 'vue-router' import basicDemo from './modules/basic-demo' import type { RouterTypes } from '~/basic' import Layout from '@/layout/index.vue' export const constantRoutes: RouterTypes = [ { path: '/redirect', component: Layout, hidden: true, children: [ { path: '/redirect/:path(.*)', component: () => import('@/views/redirect') } ] }, { path: '/login', component: () => import('@/views/login/index.vue'), hidden: true }, { path: '/404', component: () => import('@/views/error-page/404.vue'), hidden: true }, { path: '/401', component: () => import('@/views/error-page/401.vue'), hidden: true }, { path: '/', component: Layout, redirect: '/dashboard', children: [ { path: 'dashboard', name: 'Dashboard', component: () => import('@/views/dashboard/index.vue'), //using el svg icon, the elSvgIcon first when at the same time using elSvgIcon and icon meta: { title: 'Dashboard', icon: 'dashboard', affix: true } } ] }, { path: '/setting-switch', component: Layout, alwaysShow:true, meta: { title: 'Setting Switch', elSvgIcon: 'Setting' }, children: [ { path: 'index', component: () => import('@/views/setting-switch/index.vue'), name: 'SettingSwitch', meta: { title: 'Setting Switch', elSvgIcon: 'Setting' } } ] }, { path: '/nested', component: Layout, redirect: '/nested/menu1', name: 'Nested', meta: { title: 'Nested', icon: 'nested' }, children: [ { path: 'menu1', component: () => import('@/views/nested/menu1/index.vue'), // Parent router-view name: 'Menu1', meta: { title: 'Menu1' }, children: [ { path: 'menu1-1', component: () => import('@/views/nested/menu1/menu1-1/index.vue'), name: 'Menu1-1', meta: { title: 'Menu1-1' } }, { path: 'menu1-2', component: () => import('@/views/nested/menu1/menu1-2/index.vue'), name: 'Menu1-2', meta: { title: 'Menu1-2' }, children: [ { path: 'menu1-2-1', component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1/index.vue'), name: 'Menu1-2-1', meta: { title: 'Menu1-2-1' } }, { path: 'menu1-2-2', component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2/index.vue'), name: 'Menu1-2-2', meta: { title: 'Menu1-2-2' } } ] }, { path: 'menu1-3', component: () => import('@/views/nested/menu1/menu1-3/index.vue'), name: 'Menu1-3', meta: { title: 'Menu1-3' } } ] }, { path: 'menu2', component: () => import('@/views/nested/menu2/index.vue'), name: 'Menu2', meta: { title: 'menu2' } } ] }, basicDemo, { path: "/:pathMatch(.*)", redirect: "/404", hidden: true } ] //角色和code数组动态路由 export const roleCodeRoutes: RouterTypes = [ ] /** * asyncRoutes * the routes that need to be dynamically loaded based on user roles */ export const asyncRoutes: RouterTypes = [ // 404 page must be placed at the end !!! ] const router = createRouter({ history: createWebHashHistory(), scrollBehavior: () => ({ top: 0 }), routes: constantRoutes }) export default router ================================================ FILE: src/router/modules/basic-demo.ts ================================================ import Layout from '@/layout/index.vue' const BasicDemo = { path: '/basic-demo', component: Layout, meta: { title: 'Basic Demo', icon: 'eye-open' }, alwaysShow: true, children: [ { path: 'hook', component: () => import('@/views/basic-demo/hook/index.vue'), name: 'Hook', meta: { title: 'Hook' } }, { path: 'pinia', component: () => import('@/views/basic-demo/pinia/index.vue'), name: 'Pinia', meta: { title: 'Pinia' } }, { path: 'mock', component: () => import('@/views/basic-demo/mock/index.vue'), name: 'Mock', meta: { title: 'Mock' } }, { path: 'svg-icon', component: () => import('@/views/basic-demo/svg-icon/index.vue'), name: 'SvgIcon', meta: { title: 'Svg Icon' } }, { path: 'parent-children', component: () => import('@/views/basic-demo/parent-children/index.vue'), name: 'Parent', meta: { title: 'Parent Children' } }, { path: 'second-keep-alive', component: () => import('@/views/basic-demo/keep-alive/second-keep-alive.vue'), name: 'SecondKeepAlive', //cachePage: cachePage when page enter, default false //leaveRmCachePage: remove cachePage when page leave, default false meta: { title: 'Send KeepAlive', cachePage: true, closeTabRmCache: false } }, { path: 'keep-alive-group', component: () => import('@/views/basic-demo/keep-alive/index.vue'), name: 'KeepAliveGroup', //cachePage: cachePage when page enter, default false //leaveRmCachePage: remove cachePage when page leave, default false meta: { title: 'KeepAlive Group', cacheGroup: ['KeepAliveGroup', 'SecondChild', 'ThirdChild'] } }, { path: 'second-child', name: 'SecondChild', hidden: true, component: () => import('@/views/basic-demo/keep-alive/second-child.vue'), meta: { title: 'SecondChild', activeMenu: '/basic-demo/second-keep-alive' } }, { path: 'third-child', name: 'ThirdChild', hidden: true, component: () => import('@/views/basic-demo/keep-alive/third-child.vue'), meta: { title: 'ThirdChild', activeMenu: '/basic-demo/second-keep-alive' } }, //tab-keep-alive { path: 'tab-keep-alive', component: () => import('@/views/basic-demo/keep-alive/tab-keep-alive.vue'), name: 'TabKeepAlive', //closeTabRmCache: remove cachePage when tabs close, default false meta: { title: 'Tab KeepAlive', cachePage: true, closeTabRmCache: true } }, { path: 'worker', component: () => import('@/views/basic-demo/worker/index.vue'), name: 'Worker', meta: { title: 'Worker' } } ] } export default BasicDemo ================================================ FILE: src/settings.ts ================================================ import packageJson from '../package.json' import type { SettingsConfig } from '~/basic' export const settings: SettingsConfig = { title: packageJson.name, /** * @type {boolean} true | false * @description Whether show the logo in sidebar */ sidebarLogo: true, /** * @type {boolean} true | false * @description Whether show the title in Navbar */ showNavbarTitle: false, /** * @type {boolean} true | false * @description Whether show the drop-down */ ShowDropDown: true, /** * @type {boolean} true | false * @description Whether show Hamburger */ showHamburger: true, /** * @type {boolean} true | false * @description Whether show the settings right-panel */ showLeftMenu: true, /** * @type {boolean} true | false * @description Whether show TagsView */ showTagsView: true, /** * @description TagsView show number */ tagsViewNum: 6, /** * @type {boolean} true | false * @description Whether show the top Navbar */ showTopNavbar: true, /* page animation related*/ /** * @type {boolean} true | false * @description Whether need animation of main area */ mainNeedAnimation: true, /** * @type {boolean} true | false * @description Whether need nprogress */ isNeedNprogress: true, /*page login or other*/ /** * @type {boolean} true | false * @description Whether need login */ isNeedLogin: true, /** * @type {string} 'rbac'| 'roles' | 'code' */ permissionMode: 'roles', /** * @type {boolean} true | false * @description Whether open prod mock */ openProdMock: true, /** * @type {string | array} 'dev' | ['prod','test','dev'] according to the .env file props of VITE_APP_ENV * @description Need show err logs component. * The default is only used in the production env * If you want to also use it in dev, you can pass ['dev', 'test'] */ errorLog: ['prod'], /* * table height(100vh-delWindowHeight) * */ delWindowHeight: '210px', /* * setting dev token when isNeedLogin is setting false * */ tmpToken: 'tmp_token', /* * vite.config.js base config * */ viteBasePath: './', /* * i18n setting default language * en/zh * */ defaultLanguage: 'en', /* * default theme * base-theme/lighting-theme/dark-theme * */ defaultTheme: 'base-theme', /* * setting default defaultSize * large / default /small * */ defaultSize: 'small', /* * vite.config.js base config * such as * */ //平台id 2->vue3-admin-plus plateFormId: 2 } export default settings ================================================ FILE: src/store/basic.ts ================================================ import { nextTick } from 'vue' import { defineStore } from 'pinia' import type { RouterTypes } from '~/basic' import defaultSettings from '@/settings' import router, { constantRoutes } from '@/router' export const useBasicStore = defineStore('basic', { state: () => { return { token: '', getUserInfo: false, userInfo: { username: '', avatar: '' }, //user info //router allRoutes: [] as RouterTypes, buttonCodes: [], filterAsyncRoutes: [], roles: [] as Array, codes: [] as Array, //keep-alive cachedViews: [] as Array, cachedViewsDeep: [] as Array, //other sidebar: { opened: true }, //axios req collection axiosPromiseArr: [] as Array, settings: defaultSettings } }, persist: { storage: localStorage, paths: ['token'] }, actions: { remotePromiseArrByReqUrl(reqUrl) { this.$patch((state) => { state.axiosPromiseArr.forEach((fItem, index) => { if (fItem.url === reqUrl) { state.axiosPromiseArr.splice(index, 1) } }) }) }, clearPromiseArr() { this.$patch((state) => { state.axiosPromiseArr=[] }) }, setToken(data) { this.token = data }, setFilterAsyncRoutes(routes) { this.$patch((state) => { state.filterAsyncRoutes = routes state.allRoutes = constantRoutes.concat(routes) }) }, setUserInfo({ userInfo, roles, codes }) { const { username, avatar } = userInfo this.$patch((state) => { state.roles = roles state.codes = codes state.getUserInfo = true state.userInfo.username = username state.userInfo.avatar = avatar }) }, resetState() { this.$patch((state) => { state.token = '' //reset token state.roles = [] state.codes = [] //reset router state.allRoutes = [] state.buttonCodes = [] state.filterAsyncRoutes = [] //reset userInfo state.userInfo.username = '' state.userInfo.avatar = '' }) this.getUserInfo = false }, resetStateAndToLogin() { this.resetState() nextTick(() => { router.push({ path: '/login' }) }) }, setSidebarOpen(data) { this.$patch((state) => { state.sidebar.opened = data }) }, setToggleSideBar() { this.$patch((state) => { state.sidebar.opened = !state.sidebar.opened }) }, /*keepAlive缓存*/ addCachedView(view) { this.$patch((state) => { if (state.cachedViews.includes(view)) return state.cachedViews.push(view) }) }, delCachedView(view) { this.$patch((state) => { const index = state.cachedViews.indexOf(view) index > -1 && state.cachedViews.splice(index, 1) }) }, /*third keepAlive*/ addCachedViewDeep(view) { this.$patch((state) => { if (state.cachedViewsDeep.includes(view)) return state.cachedViewsDeep.push(view) }) }, delCacheViewDeep(view) { this.$patch((state) => { const index = state.cachedViewsDeep.indexOf(view) index > -1 && state.cachedViewsDeep.splice(index, 1) }) } } }) ================================================ FILE: src/store/config.ts ================================================ import { defineStore } from 'pinia' import { langTitle } from '@/hooks/use-common' import settings from '@/settings' import { toggleHtmlClass } from '@/theme/utils' import { i18n } from '@/lang' export const useConfigStore = defineStore('config', { state: () => { return { language: settings.defaultLanguage, theme: settings.defaultTheme, size: settings.defaultSize } }, persist: { storage: localStorage, paths: ['language', 'theme', 'size'] }, actions: { setTheme(data: string) { this.theme = data toggleHtmlClass(data) }, setSize(data: string) { this.size = data }, setLanguage(lang: string, title) { const { locale }: any = i18n.global this.language = lang locale.value = lang document.title = langTitle(title) // i18 page title } } }) ================================================ FILE: src/store/tags-view.ts ================================================ import { defineStore } from 'pinia' import setting from '@/settings' export const useTagsViewStore = defineStore('tagsView', { state: () => { return { visitedViews: [] //tag标签数组 } }, actions: { addVisitedView(view) { this.$patch((state: any) => { //判断添加的标签存在直接返回 if (state.visitedViews.some((v) => v.path === view.path)) return //添加的数量如果大于 setting.tagsViewNum,则替换最后一个元素,否则在visitedViews数组后插入一个元素 if (state.visitedViews.length >= setting.tagsViewNum) { state.visitedViews.pop() state.visitedViews.push( Object.assign({}, view, { title: view.meta.title || 'no-name' }) ) } else { state.visitedViews.push( Object.assign({}, view, { title: view.meta.title || 'no-name' }) ) } }) }, delVisitedView(view) { return new Promise((resolve) => { this.$patch((state: any) => { //匹配view.path元素将其删除 for (const [i, v] of state.visitedViews.entries()) { if (v.path === view.path) { state.visitedViews.splice(i, 1) break } } resolve([...state.visitedViews]) }) }) }, delOthersVisitedViews(view) { return new Promise((resolve) => { this.$patch((state) => { state.visitedViews = state.visitedViews.filter((v: ObjKeys) => { return v.meta.affix || v.path === view.path }) resolve([...state.visitedViews]) }) }) }, delAllVisitedViews() { return new Promise((resolve) => { this.$patch((state) => { // keep affix tags state.visitedViews = state.visitedViews.filter((tag: ObjKeys) => tag.meta?.affix) resolve([...state.visitedViews]) }) }) } } }) ================================================ FILE: src/styles/index.scss ================================================ //scss 语法糖包含常用的布局方式 flex column @import './scss-suger.scss'; //重置 element-plus 样式 @import 'reset-elemenet-plus.scss'; //动画文件 @import './transition.scss'; @import './project-style.scss'; //reset style body { height: 100%; margin: 0; padding: 0; font-size: 14px; } * { box-sizing: border-box; } *::before, *::after { box-sizing: border-box; } a:focus, a:active { outline: none; } a, a:focus, a:hover { cursor: pointer; color: inherit; text-decoration: none; } h1, h2, h3, h4, h5, h6 { line-height: 1; font-weight: 400; margin: 0; padding: 0; } span, output { display: inline-block; line-height: 1; } //scroll @mixin main-show-wh() { /* css 声明 */ height: calc(90vh - #{var(--nav-bar-height)} - #{var(--tag-view-height)} - #{calc(var(--app-main-padding) * 2)}); width: 100%; } .scroll-y { @include main-show-wh(); overflow-y: auto; } .scroll-x { @include main-show-wh(); overflow-x: auto; } .scroll-xy { @include main-show-wh(); overflow: auto; } ================================================ FILE: src/styles/init-loading.css ================================================ /*开屏loading样式设置*/ .loader-wrapper{ display: flex; flex-direction: column; justify-content: center; align-items: center; height: 80vh; } .loading-gif{ } .load_title{ margin-top: 30px; font-size: 16px; } ================================================ FILE: src/styles/project-style.scss ================================================ .project-page-style { background: #fff; padding: 20px; padding-top: 0; } .query-page-style { background: #fff; padding: 20px; } //汽包 .triangle { position: relative; left: 150px; width: 0; height: 0; border: 6px solid transparent; border-bottom-color: #fafafa; } //table relative .table-operation-btn { span { cursor: pointer; color: var(--el-color-primary); display: inline-block; padding: 0 6px; } } //新增底部 btn .footer-btn { position: fixed; width: calc(100% - var(--side-bar-width)); text-align: center; border-top: 1px solid var(--lineB3); background: #ffffff; padding: 12px 0; margin-left: 0; bottom: 0; z-index: 11; left: var(--side-bar-width) px; } ================================================ FILE: src/styles/reset-elemenet-plus.scss ================================================ //leave the padding of el-scroll sidebar #Sidebar{ .el-scrollbar__view { padding-bottom: 80px; } .el-scrollbar{ --el-menu-base-level-padding:15px; } } .el-dialog__body{ padding-top:0!important; } //.el-loading-spinner .circular { // display: none !important; //} //.el-loading-spinner { // background: url('../assets/gif/loading.gif') no-repeat; // background-size: 300px 300px; // width: 300px!important; // height: 300px!important; // position: fixed; // top: 50%; // left: 50%; // transform: translate(-50%, -50%); // z-index: 10; //} /* loading loading遮罩层 ------------------------------- */ .el-loading-mask { // position: relative !important; .el-loading-spinner { position: fixed !important; background: #ffffff !important; width: 200px !important; height: 120px !important; border-radius: 8px !important; left: 50% !important; transform: translate(-50%, -50%) !important; padding-top: 20px !important; box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); i { font-size: 20px; } .el-loading-text { margin: 12px 0 0 0 !important; //color: set-color(brand) !important; } } } ================================================ FILE: src/styles/scss-suger.scss ================================================ /*脱落文档流定位*/ .center-50 { //居中定位 position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; } .center-top60 { position: absolute; top: 60%; left: 50%; transform: translate(-50%, -60%); z-index: 10; } .center-top70 { position: absolute; top: 70%; left: 50%; transform: translate(-50%, -70%); z-index: 10; } .center-top80 { position: absolute; top: 80%; left: 50%; transform: translate(-50%, -80%); z-index: 10; } .center-top90 { position: absolute; top: 80%; left: 50%; transform: translate(-50%, -90%); z-index: 10; } /*fixed*/ .fixed-center-50 { //居中定位 position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10; } .fixed-center-top60 { position: fixed; top: 60%; left: 50%; transform: translate(-50%, -60%); z-index: 10; } .fixed-center-top70 { position: fixed; top: 70%; left: 50%; transform: translate(-50%, -70%); z-index: 10; } .fixed-center-top80 { position: fixed; top: 80%; left: 50%; transform: translate(-50%, -80%); z-index: 10; } .fixed-center-top90 { position: fixed; top: 90%; left: 50%; transform: translate(-50%, -90%); z-index: 10; } .fixed-center-top95 { position: fixed; top: 95%; left: 50%; transform: translate(-50%, -95%); z-index: 10; } /* flex布局 第一个字母为主轴 */ //start .rowSS { display: flex; flex-direction: row; justify-content: flex-start; align-items: flex-start; } .rowSC { display: flex; flex-direction: row; justify-content: flex-start; align-items: center; } .rowSE { display: flex; flex-direction: row; justify-content: flex-start; align-items: flex-end; } //space-between .rowBS { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start; } .rowBC { display: flex; flex-direction: row; justify-content: space-between; align-items: center; } .rowBE { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-end; } //space-around .rowAS { display: flex; flex-direction: row; justify-content: space-around; align-items: flex-start; } .rowAC { display: flex; flex-direction: row; justify-content: space-around; align-items: center; } .rowAE { display: flex; flex-direction: row; justify-content: space-around; align-items: flex-end; } //center .rowCS { display: flex; flex-direction: row; justify-content: center; align-items: flex-start; } .rowCC { display: flex; flex-direction: row; justify-content: center; align-items: center; } .rowCE { display: flex; flex-direction: row; justify-content: center; align-items: flex-end; } /*col*/ //start .columnSS { display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start; } .columnSC { display: flex; flex-direction: column; justify-content: flex-start; align-items: center; } .columnSE { display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-end; } //space-between .columnBS { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; } .columnBC { display: flex; flex-direction: column; justify-content: space-between; align-items: center; } .columnBE { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; } //space-around .columnAS { display: flex; flex-direction: column; justify-content: space-around; align-items: flex-start; } .columnAC { display: flex; flex-direction: column; justify-content: space-around; align-items: center; } .columnAE { display: flex; flex-direction: column; justify-content: space-around; align-items: flex-end; } //center .columnCS { display: flex; flex-direction: column; justify-content: center; align-items: flex-start; } .columnCC { display: flex; flex-direction: column; justify-content: center; align-items: center; } .columnCE { display: flex; flex-direction: column; justify-content: center; align-items: flex-end; } //*图标 .star-icon { color: #f56c6c; font-size: 14px; margin-right: 4px; } .fix-btn-to-bottom { position: fixed; bottom: 0; left: 50%; transform: translate(-50%, 0); z-index: 10; height: 60px; background: #fff; width: 100vw; } //table操作栏 .table-operation-btn { span { //点击样式 cursor: pointer; color: #477ef5; } } //table操作栏 .btn-click-style { //点击样式 cursor: pointer; color: #477ef5; } ================================================ FILE: src/styles/transition.scss ================================================ // vue global transition css define /* sidebar-logo-fade */ .sidebar-logo-fade-enter-active { transition: opacity var(--logo-switch-duration); } .sidebar-logo-fade-enter-from, .sidebar-logo-fade-leave-to { opacity: 0; } /* fade-transform AppMain*/ .fade-transform-leave-active, .fade-transform-enter-active { transition: all var(--page-transform-duration); } .fade-transform-enter-from { opacity: 0; transform: translateX(-10px); } .fade-transform-leave-to { opacity: 0; transform: translateX(10px); } .fade-transform-active { position: absolute; } /* breadcrumb transition */ .breadcrumb-enter-active, .breadcrumb-leave-active { transition: all var(--breadcrumb-change-duration); } .breadcrumb-enter-from, .breadcrumb-leave-active { opacity: 0; transform: translateX(10px); } .breadcrumb-leave-active { position: absolute; } ================================================ FILE: src/theme/base/custom/ct-css-vars.scss ================================================ html.base-theme { /*element-plus section */ --el-menu-active-color: #409eff; --el-menu-text-color: #bfcbd9; --el-menu-hover-text-color: var(--el-color-primary); --el-menu-bg-color: #304156; --el-menu-hover-bg-color: #263445; --el-menu-item-height: 56px; --el-menu-border-color: none; /*layout section*/ //layout --layout-border-left-color: #ddd; //Breadcrumb --breadcrumb-no-redirect: #97a8be; //Hamburger --hamburger-color: #2b2f3a; --hamburger-width: 20px; --hamburger-height: 20px; //Sidebar --sidebar-el-icon-size: 20px; --sidebar-logo-background: #2b2f3a; --sidebar-logo-color: #ff9901; --sidebar-logo-width: 32px; --sidebar-logo-height: 32px; --sidebar-logo-title-color: #fff; --side-bar-width: 210px; --side-bar-border-right-color: #eee; //TagsView --tags-view-background: #fff; --tags-view-border-bottom: #eee; --tags-view-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); --tags-view-item-background: #fff; --tags-view-item-border-color: #d8dce5; --tags-view-item-color: #495060; --tag-view-height: 32px; --tags-view-item-active-background: #42b983; --tags-view-item-active-color: #fff; --tags-view-item-active-border-color: #42b983; --tags-view-contextmenu-background: #fff; --tags-view-contextmenu-color: #333; --tags-view-contextmenu-box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); --tags-view-contextmenu-hover-background: #eee; //close-icon --tags-view-close-icon-hover-background: #b4bccc; --tags-view-close-icon-hover-color: #fff; //AppMain.vue --app-main-padding: 20px; --app-main-background: #fff; //Navbar.vue --nav-bar-height: 50px; --nav-bar-background: #fff; --nav-bar-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); --nav-bar-right-menu-background: #fff; //transition 动画 //侧边栏切换动画时长 --sideBar-switch-duration: 0.2s; //logo切换动画时长 --logo-switch-duration: 1s; //页面动画时长 --page-transform-duration: 0.2s; //面包屑导航动画时长 --breadcrumb-change-duration: 0.2s; //进度条颜色 --pregress-bar-color: transparent; } ================================================ FILE: src/theme/base/element-plus/button.scss ================================================ html.base-theme { .at-button-low { --el-button-text-color: #262626; --el-button-bg-color: #ffffff; --el-button-border-color: #d9d9d9; --el-button-outline-color: #d9d9d9; --el-button-hover-text-color: #c72210; --el-button-hover-link-text-color: #c72210; --el-button-hover-bg-color: #ffece6; --el-button-hover-border-color: transparent; --el-button-active-color: #a8150a; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffece6; --el-button-disabled-border-color: #c72210; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-middle { --el-button-text-color: #c72210; --el-button-bg-color: #ffece6; --el-button-border-color: #c72210; --el-button-outline-color: #c72210; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #c72210; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: #a8150a; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffffff; --el-button-disabled-border-color: #d9d9d9; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-height { --el-button-text-color: #ffffff; --el-button-bg-color: #c72210; --el-button-border-color: transparent; --el-button-outline-color: transparent; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #dd715b; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #f5f5f5; --el-button-disabled-border-color: transparent; //loading --el-button-loading-text-color: #ffffff; --el-button-loading-bg-color: #c72210; --el-button-loading-border-color: transparent; } .at-button-text { --el-button-text-color: #477ef5; --el-fill-color-light: transparent; --el-fill-color: transparent; --el-button-hover-text-color: #86b2f9; --el-button-active-color: #2c59cb; --el-button-disabled-text-color: #a6a6a6; //loading --el-button-loading-text-color: #477ef5; } .el-button { //default --el-button-size: 36px; height: var(--el-button-size); padding: 8px 30px; font-size: 14px; //loading .is-loading { color: var(--el-button-loading-text-color); background-color: var(--el-button-loading-bg-color); border-color: var(--el-button-loading-border-color); } } .el-button--small { --el-button-size: 27px; height: var(--el-button-size); padding: 5px 24px; font-size: 12px; } .el-button--large { --el-button-size: 40px; height: var(--el-button-size); padding: 10px 30px; font-size: 14px; } .el-button + .el-button { margin-left: 12px; } } ================================================ FILE: src/theme/base/element-plus/checkbox.scss ================================================ html.china-red { .el-checkbox { --el-checkbox-font-size: 14px; --el-checkbox-font-weight: var(--el-font-weight-primary); --el-checkbox-text-color: #262626; --el-checkbox-input-height: 14px; --el-checkbox-input-width: 14px; --el-checkbox-border-radius: var(--el-border-radius-small); --el-checkbox-bg-color: var(--el-fill-color-blank); --el-checkbox-input-border: var(--el-border); //disabled --el-checkbox-disabled-border-color: var(--el-border-color); --el-checkbox-disabled-input-fill: var(--el-fill-color-light); --el-checkbox-disabled-icon-color: var(--el-text-color-placeholder); --el-checkbox-disabled-checked-input-fill: var(--el-border-color-extra-light); --el-checkbox-disabled-checked-input-border-color: var(--el-border-color); --el-checkbox-disabled-checked-icon-color: var(--el-text-color-placeholder); //check --el-checkbox-checked-text-color: #262626; --el-checkbox-checked-input-border-color: transparent; --el-checkbox-checked-bg-color: #c72210; --el-checkbox-checked-icon-color: #ffffff; --el-checkbox-input-border-color-hover: #c72210; } } ================================================ FILE: src/theme/base/element-plus/css-vars.scss ================================================ @use 'sass:map'; @use './var' as *; @use '../../mixins/var' as *; @use '../../mixins/mixins' as *; html.china-red { color-scheme: china-red; @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-rgb($colors, $type); } @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-type($colors, $type); } //--el-color-primary: #c72210; } ================================================ FILE: src/theme/base/element-plus/form.scss ================================================ html.china-red { //date .el-date-range-picker { --el-datepicker-text-color: var(--el-text-color-regular); --el-datepicker-off-text-color: var(--el-text-color-placeholder); --el-datepicker-header-text-color: var(--el-text-color-regular); --el-datepicker-icon-color: var(--el-text-color-primary); --el-datepicker-border-color: var(--el-disabled-border-color); --el-datepicker-inner-border-color: var(--el-border-color-light); --el-datepicker-inrange-bg-color: #ffece6; --el-datepicker-inrange-hover-bg-color: var(--el-border-color-extra-light); --el-datepicker-active-color: var(--el-color-primary); --el-datepicker-hover-text-color: var(--el-color-primary); } .el-select-dropdown__item.hover, .el-select-dropdown__item:hover { background-color: #ffece6; } } ================================================ FILE: src/theme/base/element-plus/pagination.scss ================================================ html.china-red { .el-pagination { --el-text-color-regular: #8c8c8c; --el-pagination-font-size: 14px; --el-pagination-bg-color: var(--el-fill-color-blank); --el-pagination-text-color: var(--el-text-color-primary); --el-pagination-border-radius: 3px; --el-pagination-button-color: var(--el-text-color-primary); --el-pagination-button-width: 32px; --el-pagination-button-height: 32px; --el-pagination-button-disabled-color: var(--el-text-color-placeholder); --el-pagination-button-disabled-bg-color: var(--el-fill-color-blank); --el-pagination-button-bg-color: var(--el-fill-color); --el-pagination-hover-color: var(--el-color-primary); --el-pagination-height-extra-small: 24px; --el-pagination-line-height-extra-small: var(--el-pagination-height-extra-small); white-space: nowrap; padding: 2px 5px; color: var(--el-pagination-text-color); font-weight: 400; display: flex; align-items: center; } .el-pagination__total { margin-right: 16px; font-weight: 400; color: var(--el-text-color-regular); } } ================================================ FILE: src/theme/base/element-plus/redio.scss ================================================ html.china-red { .el-radio { --el-radio-font-size: var(--el-font-size-base); --el-radio-text-color: #262626; --el-radio-font-weight: var(--el-font-weight-primary); --el-radio-input-height: 14px; --el-radio-input-width: 14px; --el-radio-input-border-radius: var(--el-border-radius-circle); --el-radio-input-bg-color: var(--el-fill-color-blank); --el-radio-input-border: var(--el-border); --el-radio-input-border-color: transparent; //--el-radio-input-border-color-hover: transparent; } .el-radio__input.is-checked + .el-radio__label { color: #262626; } } ================================================ FILE: src/theme/base/element-plus/table.scss ================================================ html.china-red { .el-table { --el-table-border-color: #f0f0f0; --el-table-border: 1px solid #f0f0f0; --el-table-text-color: var(--el-text-color-regular); --el-table-header-text-color: var(--el-text-color-secondary); --el-table-row-hover-bg-color: #ffece6; --el-table-current-row-bg-color: var(--el-color-primary-light-9); --el-table-header-bg-color: #fafafa; --el-table-fixed-box-shadow: var(--el-box-shadow-light); --el-table-bg-color: var(--el-fill-color-blank); --el-table-tr-bg-color: var(--el-fill-color-blank); --el-table-expanded-cell-bg-color: var(--el-fill-color-blank); --el-table-fixed-left-column: inset 10px 0 10px -10px rgba(0, 0, 0, 0.15); --el-table-fixed-right-column: inset -10px 0 10px -10px rgba(0, 0, 0, 0.15); } } ================================================ FILE: src/theme/base/element-plus/var.scss ================================================ /* Element Chalk Variables */ @use 'sass:math'; @use 'sass:map'; @use '../../mixins/function.scss' as *; // types $types: primary, success, warning, danger, error, info; // change color $colors: () !default; $colors: map.deep-merge( ( 'white': #ffffff, 'black': #000000, 'primary': ( 'base': #c72210//#409eff ), 'success': ( 'base': #45b207 ), 'warning': ( 'base': #ec8828 ), 'danger': ( 'base': #f56c6c ), 'error': ( 'base': #d24934 ), 'info': ( 'base': #909399 ) ), $colors ); $color-white: map.get($colors, 'white') !default; $color-black: map.get($colors, 'black') !default; $color-primary: map.get($colors, 'primary', 'base') !default; $color-success: map.get($colors, 'success', 'base') !default; $color-warning: map.get($colors, 'warning', 'base') !default; $color-danger: map.get($colors, 'danger', 'base') !default; $color-error: map.get($colors, 'error', 'base') !default; $color-info: map.get($colors, 'info', 'base') !default; //$colors添加 --el-color-primary-light-7 @mixin set-color-mix-level($type, $number, $mode: 'light', $mix-color: $color-white) { $colors: map.deep-merge( ( $type: ( '#{$mode}-#{$number}': mix($mix-color, map.get($colors, $type, 'base'), math.percentage(math.div($number, 10))) ) ), $colors ) !global; } // $colors.primary.light-i @each $type in $types { @for $i from 1 through 9 { @include set-color-mix-level($type, $i, 'light', $color-white); } } ================================================ FILE: src/theme/base/index.scss ================================================ /*china-red*/ //element-plus @use "./element-plus/css-vars"; @use "./element-plus/var"; @use "./element-plus/button"; @use "./element-plus/checkbox"; @use "./element-plus/redio"; @use "./element-plus/pagination"; @use "./element-plus/form"; @use "./element-plus/table"; //custom @use "./custom/ct-css-vars"; ================================================ FILE: src/theme/china-red/custom/ct-css-vars.scss ================================================ html.china-red { --el-menu-active-color: var(--el-color-primary); --el-menu-text-color: var(--el-text-color-primary); --el-menu-hover-text-color: var(--el-menu-active-color); --el-menu-bg-color: var(--el-fill-color-blank); --el-menu-hover-bg-color: var(--el-color-primary-light-9); --el-menu-item-height: 56px; --el-menu-sub-item-height: calc(var(--el-menu-item-height) - 6px); --el-menu-horizontal-sub-item-height: 36px; --el-menu-item-font-size: var(--el-font-size-base); --el-menu-item-hover-fill: var(--el-color-primary-light-9); --el-menu-border-color: none; --el-menu-base-level-padding: 20px; --el-menu-level-padding: 20px; --el-menu-icon-width: 24px; .el-menu-item.is-active { color: var(--el-menu-active-color); background-color: #ffece6; } .el-menu-item:hover { background-color: var(--el-menu-hover-bg-color); color: var(--el-menu-active-color); } /*element-plus section */ //--el-menu-active-color: #409eff; //--el-menu-text-color: #bfcbd9; //--el-menu-hover-text-color: var(--el-color-primary); //--el-menu-bg-color: #304156; //--el-menu-hover-bg-color: #263445; //--el-menu-item-height: 56px; /*layout section*/ //layout --layout-border-left-color: #ddd; //Breadcrumb --breadcrumb-no-redirect: #97a8be; //Hamburger --hamburger-width: 20px; --hamburger-height: 20px; //Sidebar --sidebar-el-icon-size: 20px; --sidebar-logo-background: #fff; --sidebar-logo-color: #ff9901; --sidebar-logo-width: 32px; --sidebar-logo-height: 32px; --sidebar-logo-title-color: #2b2f3a; --side-bar-width: 210px; --side-bar-border-right-color: #eee; //TagsView --tags-view-background: #fff; --tags-view-border-bottom: #d8dce5; --tags-view-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); --tags-view-item-background: #fff; --tags-view-item-border-color: #d8dce5; --tags-view-item-color: #495060; --tag-view-height: 32px; --tags-view-item-active-background: #42b983; --tags-view-item-active-color: #fff; --tags-view-item-active-border-color: #42b983; --tags-view-contextmenu-background: #fff; --tags-view-contextmenu-color: #333; --tags-view-contextmenu-box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); --tags-view-contextmenu-hover-background: #eee; //close-icon --tags-view-close-icon-hover-background: #b4bccc; --tags-view-close-icon-hover-color: #fff; //AppMain.vue --app-main-padding: 20px; --app-main-background: #fff; //Navbar.vue --nav-bar-height: 50px; --nav-bar-background: #fff; --nav-bar-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); --nav-bar-right-menu-background: #fff; //transition 动画 //侧边栏切换动画时长 --sideBar-switch-duration: 0.2s; //logo切换动画时长 --logo-switch-duration: 1.5s; //页面动画时长 --page-transform-duration: 0.2s; //面包屑导航动画时长 --breadcrumb-change-duration: 0.2s; //进度条颜色 --pregress-bar-color: transparent; } ================================================ FILE: src/theme/china-red/element-plus/button.scss ================================================ html.china-red { color-scheme: china-red; .at-button-low { --el-button-text-color: #262626; --el-button-bg-color: #ffffff; --el-button-border-color: #d9d9d9; --el-button-outline-color: #d9d9d9; --el-button-hover-text-color: #c72210; --el-button-hover-link-text-color: #c72210; --el-button-hover-bg-color: #ffece6; --el-button-hover-border-color: transparent; --el-button-active-color: #a8150a; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffece6; --el-button-disabled-border-color: #c72210; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-middle { --el-button-text-color: #c72210; --el-button-bg-color: #ffece6; --el-button-border-color: #c72210; --el-button-outline-color: #c72210; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #c72210; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: #a8150a; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffffff; --el-button-disabled-border-color: #d9d9d9; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-height { --el-button-text-color: #ffffff; --el-button-bg-color: #c72210; --el-button-border-color: transparent; --el-button-outline-color: transparent; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #dd715b; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #f5f5f5; --el-button-disabled-border-color: transparent; //loading --el-button-loading-text-color: #ffffff; --el-button-loading-bg-color: #c72210; --el-button-loading-border-color: transparent; } .at-button-text { --el-button-text-color: #477ef5; --el-fill-color-light: transparent; --el-fill-color: transparent; --el-button-hover-text-color: #86b2f9; --el-button-active-color: #2c59cb; --el-button-disabled-text-color: #a6a6a6; //loading --el-button-loading-text-color: #477ef5; } .el-button { //default --el-button-size: 36px; height: var(--el-button-size); padding: 8px 30px; font-size: 14px; //loading .is-loading { color: var(--el-button-loading-text-color); background-color: var(--el-button-loading-bg-color); border-color: var(--el-button-loading-border-color); } } .el-button--small { --el-button-size: 27px; height: var(--el-button-size); padding: 5px 24px; font-size: 12px; } .el-button--large { --el-button-size: 40px; height: var(--el-button-size); padding: 10px 30px; font-size: 14px; } .el-button + .el-button { margin-left: 12px; } } ================================================ FILE: src/theme/china-red/element-plus/checkbox.scss ================================================ html.china-red { .el-checkbox { --el-checkbox-font-size: 14px; --el-checkbox-font-weight: var(--el-font-weight-primary); --el-checkbox-text-color: #262626; --el-checkbox-input-height: 14px; --el-checkbox-input-width: 14px; --el-checkbox-border-radius: var(--el-border-radius-small); --el-checkbox-bg-color: var(--el-fill-color-blank); --el-checkbox-input-border: var(--el-border); //disabled --el-checkbox-disabled-border-color: var(--el-border-color); --el-checkbox-disabled-input-fill: var(--el-fill-color-light); --el-checkbox-disabled-icon-color: var(--el-text-color-placeholder); --el-checkbox-disabled-checked-input-fill: var(--el-border-color-extra-light); --el-checkbox-disabled-checked-input-border-color: var(--el-border-color); --el-checkbox-disabled-checked-icon-color: var(--el-text-color-placeholder); //check --el-checkbox-checked-text-color: #262626; --el-checkbox-checked-input-border-color: transparent; --el-checkbox-checked-bg-color: #c72210; --el-checkbox-checked-icon-color: #ffffff; --el-checkbox-input-border-color-hover: #c72210; } } ================================================ FILE: src/theme/china-red/element-plus/css-vars.scss ================================================ @use 'sass:map'; @use './var' as *; @use '../../mixins/var' as *; @use '../../mixins/mixins' as *; html.china-red { color-scheme: china-red; @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-rgb($colors, $type); } @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-type($colors, $type); } //--el-color-primary: #c72210; } ================================================ FILE: src/theme/china-red/element-plus/form.scss ================================================ html.china-red { //date .el-date-range-picker { --el-datepicker-text-color: var(--el-text-color-regular); --el-datepicker-off-text-color: var(--el-text-color-placeholder); --el-datepicker-header-text-color: var(--el-text-color-regular); --el-datepicker-icon-color: var(--el-text-color-primary); --el-datepicker-border-color: var(--el-disabled-border-color); --el-datepicker-inner-border-color: var(--el-border-color-light); --el-datepicker-inrange-bg-color: #ffece6; --el-datepicker-inrange-hover-bg-color: var(--el-border-color-extra-light); --el-datepicker-active-color: var(--el-color-primary); --el-datepicker-hover-text-color: var(--el-color-primary); } .el-select-dropdown__item.hover, .el-select-dropdown__item:hover { background-color: #ffece6; } } ================================================ FILE: src/theme/china-red/element-plus/pagination.scss ================================================ html.china-red { .el-pagination { --el-text-color-regular: #8c8c8c; --el-pagination-font-size: 14px; --el-pagination-bg-color: var(--el-fill-color-blank); --el-pagination-text-color: var(--el-text-color-primary); --el-pagination-border-radius: 3px; --el-pagination-button-color: var(--el-text-color-primary); --el-pagination-button-width: 32px; --el-pagination-button-height: 32px; --el-pagination-button-disabled-color: var(--el-text-color-placeholder); --el-pagination-button-disabled-bg-color: var(--el-fill-color-blank); --el-pagination-button-bg-color: var(--el-fill-color); --el-pagination-hover-color: var(--el-color-primary); --el-pagination-height-extra-small: 24px; --el-pagination-line-height-extra-small: var(--el-pagination-height-extra-small); white-space: nowrap; padding: 2px 5px; color: var(--el-pagination-text-color); font-weight: 400; display: flex; align-items: center; } .el-pagination__total { margin-right: 16px; font-weight: 400; color: var(--el-text-color-regular); } } ================================================ FILE: src/theme/china-red/element-plus/redio.scss ================================================ html.china-red { .el-radio { --el-radio-font-size: var(--el-font-size-base); --el-radio-text-color: #262626; --el-radio-font-weight: var(--el-font-weight-primary); --el-radio-input-height: 14px; --el-radio-input-width: 14px; --el-radio-input-border-radius: var(--el-border-radius-circle); --el-radio-input-bg-color: var(--el-fill-color-blank); --el-radio-input-border: var(--el-border); --el-radio-input-border-color: transparent; //--el-radio-input-border-color-hover: transparent; } .el-radio__input.is-checked + .el-radio__label { color: #262626; } } ================================================ FILE: src/theme/china-red/element-plus/table.scss ================================================ html.china-red { .el-table { --el-table-border-color: #f0f0f0; --el-table-border: 1px solid #f0f0f0; --el-table-text-color: var(--el-text-color-regular); --el-table-header-text-color: var(--el-text-color-secondary); --el-table-row-hover-bg-color: #ffece6; --el-table-current-row-bg-color: var(--el-color-primary-light-9); --el-table-header-bg-color: #fafafa; --el-table-fixed-box-shadow: var(--el-box-shadow-light); --el-table-bg-color: var(--el-fill-color-blank); --el-table-tr-bg-color: var(--el-fill-color-blank); --el-table-expanded-cell-bg-color: var(--el-fill-color-blank); --el-table-fixed-left-column: inset 10px 0 10px -10px rgba(0, 0, 0, 0.15); --el-table-fixed-right-column: inset -10px 0 10px -10px rgba(0, 0, 0, 0.15); } } ================================================ FILE: src/theme/china-red/element-plus/var.scss ================================================ /* Element Chalk Variables */ @use 'sass:math'; @use 'sass:map'; @use '../../mixins/function.scss' as *; // types $types: primary, success, warning, danger, error, info; // change color $colors: () !default; $colors: map.deep-merge( ( 'white': #ffffff, 'black': #000000, 'primary': ( 'base': #c72210//#409eff ), 'success': ( 'base': #45b207 ), 'warning': ( 'base': #ec8828 ), 'danger': ( 'base': #f56c6c ), 'error': ( 'base': #d24934 ), 'info': ( 'base': #909399 ) ), $colors ); $color-white: map.get($colors, 'white') !default; $color-black: map.get($colors, 'black') !default; $color-primary: map.get($colors, 'primary', 'base') !default; $color-success: map.get($colors, 'success', 'base') !default; $color-warning: map.get($colors, 'warning', 'base') !default; $color-danger: map.get($colors, 'danger', 'base') !default; $color-error: map.get($colors, 'error', 'base') !default; $color-info: map.get($colors, 'info', 'base') !default; //$colors添加 --el-color-primary-light-7 @mixin set-color-mix-level($type, $number, $mode: 'light', $mix-color: $color-white) { $colors: map.deep-merge( ( $type: ( '#{$mode}-#{$number}': mix($mix-color, map.get($colors, $type, 'base'), math.percentage(math.div($number, 10))) ) ), $colors ) !global; } // $colors.primary.light-i @each $type in $types { @for $i from 1 through 9 { @include set-color-mix-level($type, $i, 'light', $color-white); } } ================================================ FILE: src/theme/china-red/index.scss ================================================ /*china-red*/ //element-plus @use "./element-plus/css-vars"; @use "./element-plus/var"; @use "./element-plus/button"; @use "./element-plus/checkbox"; @use "./element-plus/redio"; @use "./element-plus/pagination"; @use "./element-plus/form"; @use "./element-plus/table"; //custom @use "./custom/ct-css-vars"; ================================================ FILE: src/theme/dark/custom/ct-css-vars.scss ================================================ html.dark { /*element-plus section */ --el-menu-active-color: #409eff; --el-menu-text-color: #bfcbd9; --el-menu-hover-text-color: var(--el-color-primary); --el-menu-bg-color: #304156; --el-menu-hover-bg-color: #263445; --el-menu-item-height: 56px; --el-menu-border-color: none; /*layout section*/ //layout --layout-border-left-color: #ddd; //Breadcrumb --breadcrumb-no-redirect: #97a8be; //Hamburger --hamburger-color: #fff; --hamburger-width: 20px; --hamburger-height: 20px; --el-text-color-primary: #fff; //Sidebar --sidebar-el-icon-size: 20px; --sidebar-logo-background: #2b2f3a; --sidebar-logo-color: #ff9901; --sidebar-logo-width: 32px; --sidebar-logo-height: 32px; --sidebar-logo-title-color: #fff; --side-bar-width: 210px; --side-bar-border-right-color: #2b2f3a; //TagsView --tags-view-background: #304156; --tags-view-border-bottom: #2b2f3a; --tags-view-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); --tags-view-item-background: #fff; --tags-view-item-border-color: #d8dce5; --tags-view-item-color: #495060; --tag-view-height: 32px; --tags-view-item-active-background: #42b983; --tags-view-item-active-color: #fff; --tags-view-item-active-border-color: #42b983; --tags-view-contextmenu-background: #fff; --tags-view-contextmenu-color: #333; --tags-view-contextmenu-box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); --tags-view-contextmenu-hover-background: #eee; //close-icon --tags-view-close-icon-hover-background: #b4bccc; --tags-view-close-icon-hover-color: #fff; //AppMain.vue --app-main-padding: 0px; --app-main-background: #304156; //Navbar.vue --nav-bar-height: 50px; --nav-bar-background: #2b2f3a; --nav-bar-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); --nav-bar-right-menu-background: #2b2f3a; //transition 动画 //侧边栏切换动画时长 --sideBar-switch-duration: 0.2s; //logo切换动画时长 --logo-switch-duration: 1s; //页面动画时长 --page-transform-duration: 0.2s; //面包屑导航动画时长 --breadcrumb-change-duration: 0.2s; //进度条颜色 --pregress-bar-color: transparent; } ================================================ FILE: src/theme/dark/element-plus/button.scss ================================================ html.china-red { color-scheme: china-red; .at-button-low { --el-button-text-color: #262626; --el-button-bg-color: #ffffff; --el-button-border-color: #d9d9d9; --el-button-outline-color: #d9d9d9; --el-button-hover-text-color: #c72210; --el-button-hover-link-text-color: #c72210; --el-button-hover-bg-color: #ffece6; --el-button-hover-border-color: transparent; --el-button-active-color: #a8150a; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffece6; --el-button-disabled-border-color: #c72210; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-middle { --el-button-text-color: #c72210; --el-button-bg-color: #ffece6; --el-button-border-color: #c72210; --el-button-outline-color: #c72210; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #c72210; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: #a8150a; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffffff; --el-button-disabled-border-color: #d9d9d9; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-height { --el-button-text-color: #ffffff; --el-button-bg-color: #c72210; --el-button-border-color: transparent; --el-button-outline-color: transparent; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #dd715b; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #f5f5f5; --el-button-disabled-border-color: transparent; //loading --el-button-loading-text-color: #ffffff; --el-button-loading-bg-color: #c72210; --el-button-loading-border-color: transparent; } .at-button-text { --el-button-text-color: #477ef5; --el-fill-color-light: transparent; --el-fill-color: transparent; --el-button-hover-text-color: #86b2f9; --el-button-active-color: #2c59cb; --el-button-disabled-text-color: #a6a6a6; //loading --el-button-loading-text-color: #477ef5; } .el-button { //default --el-button-size: 36px; height: var(--el-button-size); padding: 8px 30px; font-size: 14px; //loading .is-loading { color: var(--el-button-loading-text-color); background-color: var(--el-button-loading-bg-color); border-color: var(--el-button-loading-border-color); } } .el-button--small { --el-button-size: 27px; height: var(--el-button-size); padding: 5px 24px; font-size: 12px; } .el-button--large { --el-button-size: 40px; height: var(--el-button-size); padding: 10px 30px; font-size: 14px; } .el-button + .el-button { margin-left: 12px; } } ================================================ FILE: src/theme/dark/element-plus/checkbox.scss ================================================ html.china-red { .el-checkbox { --el-checkbox-font-size: 14px; --el-checkbox-font-weight: var(--el-font-weight-primary); --el-checkbox-text-color: #262626; --el-checkbox-input-height: 14px; --el-checkbox-input-width: 14px; --el-checkbox-border-radius: var(--el-border-radius-small); --el-checkbox-bg-color: var(--el-fill-color-blank); --el-checkbox-input-border: var(--el-border); //disabled --el-checkbox-disabled-border-color: var(--el-border-color); --el-checkbox-disabled-input-fill: var(--el-fill-color-light); --el-checkbox-disabled-icon-color: var(--el-text-color-placeholder); --el-checkbox-disabled-checked-input-fill: var(--el-border-color-extra-light); --el-checkbox-disabled-checked-input-border-color: var(--el-border-color); --el-checkbox-disabled-checked-icon-color: var(--el-text-color-placeholder); //check --el-checkbox-checked-text-color: #262626; --el-checkbox-checked-input-border-color: transparent; --el-checkbox-checked-bg-color: #c72210; --el-checkbox-checked-icon-color: #ffffff; --el-checkbox-input-border-color-hover: #c72210; } } ================================================ FILE: src/theme/dark/element-plus/css-vars.css ================================================ ================================================ FILE: src/theme/dark/element-plus/css-vars.scss ================================================ @use 'sass:map'; @use './var' as *; @use '../../mixins/var' as *; @use '../../mixins/mixins' as *; html.china-red { color-scheme: china-red; @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-rgb($colors, $type); } @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-type($colors, $type); } //--el-color-primary: #c72210; } ================================================ FILE: src/theme/dark/element-plus/form.scss ================================================ html.china-red { //date .el-date-range-picker { --el-datepicker-text-color: var(--el-text-color-regular); --el-datepicker-off-text-color: var(--el-text-color-placeholder); --el-datepicker-header-text-color: var(--el-text-color-regular); --el-datepicker-icon-color: var(--el-text-color-primary); --el-datepicker-border-color: var(--el-disabled-border-color); --el-datepicker-inner-border-color: var(--el-border-color-light); --el-datepicker-inrange-bg-color: #ffece6; --el-datepicker-inrange-hover-bg-color: var(--el-border-color-extra-light); --el-datepicker-active-color: var(--el-color-primary); --el-datepicker-hover-text-color: var(--el-color-primary); } .el-select-dropdown__item.hover, .el-select-dropdown__item:hover { background-color: #ffece6; } } ================================================ FILE: src/theme/dark/element-plus/pagination.scss ================================================ html.china-red { .el-pagination { --el-text-color-regular: #8c8c8c; --el-pagination-font-size: 14px; --el-pagination-bg-color: var(--el-fill-color-blank); --el-pagination-text-color: var(--el-text-color-primary); --el-pagination-border-radius: 3px; --el-pagination-button-color: var(--el-text-color-primary); --el-pagination-button-width: 32px; --el-pagination-button-height: 32px; --el-pagination-button-disabled-color: var(--el-text-color-placeholder); --el-pagination-button-disabled-bg-color: var(--el-fill-color-blank); --el-pagination-button-bg-color: var(--el-fill-color); --el-pagination-hover-color: var(--el-color-primary); --el-pagination-height-extra-small: 24px; --el-pagination-line-height-extra-small: var(--el-pagination-height-extra-small); white-space: nowrap; padding: 2px 5px; color: var(--el-pagination-text-color); font-weight: 400; display: flex; align-items: center; } .el-pagination__total { margin-right: 16px; font-weight: 400; color: var(--el-text-color-regular); } } ================================================ FILE: src/theme/dark/element-plus/redio.scss ================================================ html.china-red { .el-radio { --el-radio-font-size: var(--el-font-size-base); --el-radio-text-color: #262626; --el-radio-font-weight: var(--el-font-weight-primary); --el-radio-input-height: 14px; --el-radio-input-width: 14px; --el-radio-input-border-radius: var(--el-border-radius-circle); --el-radio-input-bg-color: var(--el-fill-color-blank); --el-radio-input-border: var(--el-border); --el-radio-input-border-color: transparent; //--el-radio-input-border-color-hover: transparent; } .el-radio__input.is-checked + .el-radio__label { color: #262626; } } ================================================ FILE: src/theme/dark/element-plus/table.scss ================================================ html.china-red { .el-table { --el-table-border-color: #f0f0f0; --el-table-border: 1px solid #f0f0f0; --el-table-text-color: var(--el-text-color-regular); --el-table-header-text-color: var(--el-text-color-secondary); --el-table-row-hover-bg-color: #ffece6; --el-table-current-row-bg-color: var(--el-color-primary-light-9); --el-table-header-bg-color: #fafafa; --el-table-fixed-box-shadow: var(--el-box-shadow-light); --el-table-bg-color: var(--el-fill-color-blank); --el-table-tr-bg-color: var(--el-fill-color-blank); --el-table-expanded-cell-bg-color: var(--el-fill-color-blank); --el-table-fixed-left-column: inset 10px 0 10px -10px rgba(0, 0, 0, 0.15); --el-table-fixed-right-column: inset -10px 0 10px -10px rgba(0, 0, 0, 0.15); } } ================================================ FILE: src/theme/dark/element-plus/var.scss ================================================ /* Element Chalk Variables */ @use 'sass:math'; @use 'sass:map'; @use '../../mixins/function.scss' as *; // types $types: primary, success, warning, danger, error, info; // change color $colors: () !default; $colors: map.deep-merge( ( 'white': #ffffff, 'black': #000000, 'primary': ( 'base': #c72210//#409eff ), 'success': ( 'base': #45b207 ), 'warning': ( 'base': #ec8828 ), 'danger': ( 'base': #f56c6c ), 'error': ( 'base': #d24934 ), 'info': ( 'base': #909399 ) ), $colors ); $color-white: map.get($colors, 'white') !default; $color-black: map.get($colors, 'black') !default; $color-primary: map.get($colors, 'primary', 'base') !default; $color-success: map.get($colors, 'success', 'base') !default; $color-warning: map.get($colors, 'warning', 'base') !default; $color-danger: map.get($colors, 'danger', 'base') !default; $color-error: map.get($colors, 'error', 'base') !default; $color-info: map.get($colors, 'info', 'base') !default; //$colors添加 --el-color-primary-light-7 @mixin set-color-mix-level($type, $number, $mode: 'light', $mix-color: $color-white) { $colors: map.deep-merge( ( $type: ( '#{$mode}-#{$number}': mix($mix-color, map.get($colors, $type, 'base'), math.percentage(math.div($number, 10))) ) ), $colors ) !global; } // $colors.primary.light-i @each $type in $types { @for $i from 1 through 9 { @include set-color-mix-level($type, $i, 'light', $color-white); } } ================================================ FILE: src/theme/dark/index.scss ================================================ /*china-red*/ //element-plus //@use "./element-plus/css-vars"; //@use "./element-plus/var"; //@use "./element-plus/button"; //@use "./element-plus/checkbox"; //@use "./element-plus/redio"; //@use "./element-plus/pagination"; //@use "./element-plus/form"; //@use "./element-plus/table"; //custom @use "./custom/ct-css-vars"; ================================================ FILE: src/theme/index.css ================================================ ================================================ FILE: src/theme/index.scss ================================================ // we can add this to custom namespace, default is 'el' //@forward "element-plus/theme-chalk/src/mixins/config.scss" with ( // $namespace: "el" //); //base-theme @use "./base"; //theme style such as dark-theme lighting @use "./dark"; @use "./lighting"; @use "./china-red"; ================================================ FILE: src/theme/lighting/custom/ct-css-vars.scss ================================================ html.lighting-theme { /*element-plus section */ //--el-menu-active-color: #409eff; //--el-menu-text-color: #bfcbd9; //--el-menu-hover-text-color: var(--el-color-primary); //--el-menu-bg-color: #304156; //--el-menu-hover-bg-color: #263445; //--el-menu-item-height: 56px; --el-menu-border-color: none; /*layout section*/ //layout --layout-border-left-color: #ddd; //Breadcrumb --breadcrumb-no-redirect: #97a8be; //Hamburger --hamburger-color: #2b2f3a; --hamburger-width: 20px; --hamburger-height: 20px; //Sidebar --sidebar-el-icon-size: 20px; --sidebar-logo-background: #fff; --sidebar-logo-color: #ff9901; --sidebar-logo-width: 32px; --sidebar-logo-height: 32px; --sidebar-logo-title-color: #2b2f3a; --side-bar-width: 210px; --side-bar-border-right-color: #eee; //TagsView --tags-view-background: #fff; --tags-view-border-bottom: #d8dce5; --tags-view-box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04); --tags-view-item-background: #fff; --tags-view-item-border-color: #d8dce5; --tags-view-item-color: #495060; --tag-view-height: 32px; --tags-view-item-active-background: #42b983; --tags-view-item-active-color: #fff; --tags-view-item-active-border-color: #42b983; --tags-view-contextmenu-background: #fff; --tags-view-contextmenu-color: #333; --tags-view-contextmenu-box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); --tags-view-contextmenu-hover-background: #eee; //close-icon --tags-view-close-icon-hover-background: #b4bccc; --tags-view-close-icon-hover-color: #fff; //AppMain.vue --app-main-padding: 20px; --app-main-background: #fff; //Navbar.vue --nav-bar-height: 50px; --nav-bar-background: #fff; --nav-bar-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); --nav-bar-right-menu-background: #fff; //transition 动画 //侧边栏切换动画时长 --sideBar-switch-duration: 0.2s; //logo切换动画时长 --logo-switch-duration: 0.5s; //页面动画时长 --page-transform-duration: 0.2s; //面包屑导航动画时长 --breadcrumb-change-duration: 0.2s; //进度条颜色 --pregress-bar-color: transparent; } ================================================ FILE: src/theme/lighting/element-plus/button.scss ================================================ html.china-red { color-scheme: china-red; .at-button-low { --el-button-text-color: #262626; --el-button-bg-color: #ffffff; --el-button-border-color: #d9d9d9; --el-button-outline-color: #d9d9d9; --el-button-hover-text-color: #c72210; --el-button-hover-link-text-color: #c72210; --el-button-hover-bg-color: #ffece6; --el-button-hover-border-color: transparent; --el-button-active-color: #a8150a; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffece6; --el-button-disabled-border-color: #c72210; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-middle { --el-button-text-color: #c72210; --el-button-bg-color: #ffece6; --el-button-border-color: #c72210; --el-button-outline-color: #c72210; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #c72210; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: #a8150a; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #ffffff; --el-button-disabled-border-color: #d9d9d9; //loading --el-button-loading-text-color: #c72210; --el-button-loading-bg-color: #ffece6; --el-button-loading-border-color: #c72210; } .at-button-height { --el-button-text-color: #ffffff; --el-button-bg-color: #c72210; --el-button-border-color: transparent; --el-button-outline-color: transparent; --el-button-hover-text-color: #ffffff; --el-button-hover-link-text-color: #ffffff; --el-button-hover-bg-color: #dd715b; --el-button-hover-border-color: #c72210; --el-button-active-color: #ffffff; --el-button-active-bg-color: #a8150a; --el-button-active-border-color: transparent; --el-button-disabled-text-color: #a6a6a6; --el-button-disabled-bg-color: #f5f5f5; --el-button-disabled-border-color: transparent; //loading --el-button-loading-text-color: #ffffff; --el-button-loading-bg-color: #c72210; --el-button-loading-border-color: transparent; } .at-button-text { --el-button-text-color: #477ef5; --el-fill-color-light: transparent; --el-fill-color: transparent; --el-button-hover-text-color: #86b2f9; --el-button-active-color: #2c59cb; --el-button-disabled-text-color: #a6a6a6; //loading --el-button-loading-text-color: #477ef5; } .el-button { //default --el-button-size: 36px; height: var(--el-button-size); padding: 8px 30px; font-size: 14px; //loading .is-loading { color: var(--el-button-loading-text-color); background-color: var(--el-button-loading-bg-color); border-color: var(--el-button-loading-border-color); } } .el-button--small { --el-button-size: 27px; height: var(--el-button-size); padding: 5px 24px; font-size: 12px; } .el-button--large { --el-button-size: 40px; height: var(--el-button-size); padding: 10px 30px; font-size: 14px; } .el-button + .el-button { margin-left: 12px; } } ================================================ FILE: src/theme/lighting/element-plus/checkbox.scss ================================================ html.china-red { .el-checkbox { --el-checkbox-font-size: 14px; --el-checkbox-font-weight: var(--el-font-weight-primary); --el-checkbox-text-color: #262626; --el-checkbox-input-height: 14px; --el-checkbox-input-width: 14px; --el-checkbox-border-radius: var(--el-border-radius-small); --el-checkbox-bg-color: var(--el-fill-color-blank); --el-checkbox-input-border: var(--el-border); //disabled --el-checkbox-disabled-border-color: var(--el-border-color); --el-checkbox-disabled-input-fill: var(--el-fill-color-light); --el-checkbox-disabled-icon-color: var(--el-text-color-placeholder); --el-checkbox-disabled-checked-input-fill: var(--el-border-color-extra-light); --el-checkbox-disabled-checked-input-border-color: var(--el-border-color); --el-checkbox-disabled-checked-icon-color: var(--el-text-color-placeholder); //check --el-checkbox-checked-text-color: #262626; --el-checkbox-checked-input-border-color: transparent; --el-checkbox-checked-bg-color: #c72210; --el-checkbox-checked-icon-color: #ffffff; --el-checkbox-input-border-color-hover: #c72210; } } ================================================ FILE: src/theme/lighting/element-plus/css-vars.css ================================================ ================================================ FILE: src/theme/lighting/element-plus/css-vars.scss ================================================ @use 'sass:map'; @use './var' as *; @use '../../mixins/var' as *; @use '../../mixins/mixins' as *; html.china-red { color-scheme: china-red; @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-rgb($colors,$type); } @each $type in (primary, success, warning, danger, error, info) { @include set-css-color-type($colors, $type); } //--el-color-primary: #c72210; } ================================================ FILE: src/theme/lighting/element-plus/form.scss ================================================ html.china-red { //date .el-date-range-picker { --el-datepicker-text-color: var(--el-text-color-regular); --el-datepicker-off-text-color: var(--el-text-color-placeholder); --el-datepicker-header-text-color: var(--el-text-color-regular); --el-datepicker-icon-color: var(--el-text-color-primary); --el-datepicker-border-color: var(--el-disabled-border-color); --el-datepicker-inner-border-color: var(--el-border-color-light); --el-datepicker-inrange-bg-color: #ffece6; --el-datepicker-inrange-hover-bg-color: var(--el-border-color-extra-light); --el-datepicker-active-color: var(--el-color-primary); --el-datepicker-hover-text-color: var(--el-color-primary); } .el-select-dropdown__item.hover, .el-select-dropdown__item:hover { background-color: #ffece6; } } ================================================ FILE: src/theme/lighting/element-plus/pagination.scss ================================================ html.china-red { .el-pagination { --el-text-color-regular: #8c8c8c; --el-pagination-font-size: 14px; --el-pagination-bg-color: var(--el-fill-color-blank); --el-pagination-text-color: var(--el-text-color-primary); --el-pagination-border-radius: 3px; --el-pagination-button-color: var(--el-text-color-primary); --el-pagination-button-width: 32px; --el-pagination-button-height: 32px; --el-pagination-button-disabled-color: var(--el-text-color-placeholder); --el-pagination-button-disabled-bg-color: var(--el-fill-color-blank); --el-pagination-button-bg-color: var(--el-fill-color); --el-pagination-hover-color: var(--el-color-primary); --el-pagination-height-extra-small: 24px; --el-pagination-line-height-extra-small: var(--el-pagination-height-extra-small); white-space: nowrap; padding: 2px 5px; color: var(--el-pagination-text-color); font-weight: 400; display: flex; align-items: center; } .el-pagination__total { margin-right: 16px; font-weight: 400; color: var(--el-text-color-regular); } } ================================================ FILE: src/theme/lighting/element-plus/redio.scss ================================================ html.china-red { .el-radio { --el-radio-font-size: var(--el-font-size-base); --el-radio-text-color: #262626; --el-radio-font-weight: var(--el-font-weight-primary); --el-radio-input-height: 14px; --el-radio-input-width: 14px; --el-radio-input-border-radius: var(--el-border-radius-circle); --el-radio-input-bg-color: var(--el-fill-color-blank); --el-radio-input-border: var(--el-border); --el-radio-input-border-color: transparent; //--el-radio-input-border-color-hover: transparent; } .el-radio__input.is-checked + .el-radio__label { color: #262626; } } ================================================ FILE: src/theme/lighting/element-plus/table.scss ================================================ html.china-red { .el-table { --el-table-border-color: #f0f0f0; --el-table-border: 1px solid #f0f0f0; --el-table-text-color: var(--el-text-color-regular); --el-table-header-text-color: var(--el-text-color-secondary); --el-table-row-hover-bg-color: #ffece6; --el-table-current-row-bg-color: var(--el-color-primary-light-9); --el-table-header-bg-color: #fafafa; --el-table-fixed-box-shadow: var(--el-box-shadow-light); --el-table-bg-color: var(--el-fill-color-blank); --el-table-tr-bg-color: var(--el-fill-color-blank); --el-table-expanded-cell-bg-color: var(--el-fill-color-blank); --el-table-fixed-left-column: inset 10px 0 10px -10px rgba(0, 0, 0, 0.15); --el-table-fixed-right-column: inset -10px 0 10px -10px rgba(0, 0, 0, 0.15); } } ================================================ FILE: src/theme/lighting/element-plus/var.scss ================================================ /* Element Chalk Variables */ @use 'sass:math'; @use 'sass:map'; @use '../../mixins/function.scss' as *; // types $types: primary, success, warning, danger, error, info; // change color $colors: () !default; $colors: map.deep-merge( ( 'white': #ffffff, 'black': #000000, 'primary': ( 'base': #c72210//#409eff ), 'success': ( 'base': #45b207 ), 'warning': ( 'base': #ec8828 ), 'danger': ( 'base': #f56c6c ), 'error': ( 'base': #d24934 ), 'info': ( 'base': #909399 ) ), $colors ); $color-white: map.get($colors, 'white') !default; $color-black: map.get($colors, 'black') !default; $color-primary: map.get($colors, 'primary', 'base') !default; $color-success: map.get($colors, 'success', 'base') !default; $color-warning: map.get($colors, 'warning', 'base') !default; $color-danger: map.get($colors, 'danger', 'base') !default; $color-error: map.get($colors, 'error', 'base') !default; $color-info: map.get($colors, 'info', 'base') !default; //$colors添加 --el-color-primary-light-7 @mixin set-color-mix-level($type, $number, $mode: 'light', $mix-color: $color-white) { $colors: map.deep-merge( ( $type: ( '#{$mode}-#{$number}': mix($mix-color, map.get($colors, $type, 'base'), math.percentage(math.div($number, 10))) ) ), $colors ) !global; } // $colors.primary.light-i @each $type in $types { @for $i from 1 through 9 { @include set-color-mix-level($type, $i, 'light', $color-white); } } ================================================ FILE: src/theme/lighting/index.scss ================================================ /*china-red*/ //element-plus //@use "./element-plus/css-vars"; //@use "./element-plus/var"; //@use "./element-plus/button"; //@use "./element-plus/checkbox"; //@use "./element-plus/redio"; //@use "./element-plus/pagination"; //@use "./element-plus/form"; //@use "./element-plus/table"; //custom @use "./custom/ct-css-vars"; ================================================ FILE: src/theme/mixins/_var.scss ================================================ /*var mixin*/ @use 'sass:map'; @use 'config'; @use 'function' as *; // set css var value, because we need translate value to string // for example: // @include set-css-var-value(('color', 'primary'), red); // --el-color: red; // --el-$name-: $value; @mixin set-css-var-value($name, $value) { #{joinVarName($name)}: #{$value}; } @mixin set-css-color-type($colors, $type) { @include set-css-var-value(('color', $type), map.get($colors, $type, 'base')); @each $i in (3, 5, 7, 8, 9) { // --el-color-primary-light-7: #c6e2ff; @include set-css-var-value(('color', $type, 'light', $i), map.get($colors, $type, 'light-#{$i}')); } //@include set-css-var-value( // ('color', $type, 'dark-2'), // map.get($colors, $type, 'dark-2') //); } //el-$name-$attribute-$value @mixin set-component-css-var($name, $variables) { @each $attribute, $value in $variables { @if $attribute == 'default' { #{getCssVarName($name)}: #{$value}; } @else { #{getCssVarName($name, $attribute)}: #{$value}; } } } // --el-color-error-rgb: 245, 108, 108; // --el-color-$type-rgb: 245, 108, 108; @mixin set-css-color-rgb($colors, $type) { $color: map.get($colors, $type, 'base'); @include set-css-var-value(('color', $type, 'rgb'), #{red($color), green($color), blue($color)}); } // generate css var from existing css var // for example: // @include css-var-from-global(('button', 'text-color'), ('color', $type)) // --el-button-text-color: var(--el-color-#{$type}); @mixin css-var-from-global($var, $gVar) { $varName: joinVarName($var); $gVarName: joinVarName($gVar); #{$varName}: var(#{$gVarName}); } ================================================ FILE: src/theme/mixins/config.scss ================================================ $namespace: 'el' !default; $common-separator: '-' !default; $element-separator: '__' !default; $modifier-separator: '--' !default; $state-prefix: 'is-' !default; ================================================ FILE: src/theme/mixins/function.scss ================================================ @use 'config'; // getCssVarName('button', 'text-color') => '--el-button-text-color' @function getCssVarName($args...) { @return joinVarName($args); } // getCssVar('button', 'text-color') => var(--el-button-text-color) @function getCssVar($args...) { @return var(#{joinVarName($args)}); } @function selectorToString($selector) { $selector: inspect($selector); $selector: str-slice($selector, 2, -2); @return $selector; } @function containsModifier($selector) { $selector: selectorToString($selector); @if str-index($selector, config.$modifier-separator) { @return true; } @else { @return false; } } @function hitAllSpecialNestRule($selector) { @return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector); } @function containWhenFlag($selector) { $selector: selectorToString($selector); @if str-index($selector, '.' + config.$state-prefix) { @return true; } @else { @return false; } } @function containPseudoClass($selector) { $selector: selectorToString($selector); @if str-index($selector, ':') { @return true; } @else { @return false; } } // join var name // joinVarName(('button', 'text-color')) => '--el-button-text-color' @function joinVarName($list) { $name: '--' + config.$namespace; @each $item in $list { @if $item != '' { $name: $name + '-' + $item; } } @return $name; } ================================================ FILE: src/theme/mixins/mixins.scss ================================================ //input function @use 'function' as *; @forward 'function'; @forward 'config'; @use 'config' as *; // el-button{} @mixin b($block) { $B: $namespace + '-' + $block !global; .#{$B} { @content; } } @mixin e($element) { $E: $element !global; $selector: &; $currentSelector: ''; @each $unit in $element { //el-button__text $currentSelector: #{$currentSelector + '.' + $B + $element-separator + $unit + ','}; } @if hitAllSpecialNestRule($selector) { @at-root { #{$selector} { #{$currentSelector} { @content; } } } } @else { @at-root { #{$currentSelector} { @content; } } } } @mixin m($modifier) { $selector: &; $currentSelector: ''; @each $unit in $modifier { $currentSelector: #{$currentSelector + $selector + $modifier-separator + $unit + ','}; } @at-root { #{$currentSelector} { @content; } } } @mixin when($state) { @at-root { &.#{$state-prefix + $state} { @content; } } } ================================================ FILE: src/theme/utils/change-theme.ts ================================================ export const toggleHtmlClass = (className) => { document.querySelectorAll('html')[0].className = className } ================================================ FILE: src/theme/utils/index.ts ================================================ export * from './change-theme' ================================================ FILE: src/utils/axios-req.ts ================================================ import axios from 'axios' import { ElLoading, ElMessage, ElMessageBox } from 'element-plus' import { useBasicStore } from '@/store/basic' //使用axios.create()创建一个axios请求实例 const service = axios.create() let loadingInstance: any = null //loading实例 let tempReqUrlSave = '' let authorTipDoor = true const noAuthDill = () => { authorTipDoor = false ElMessageBox.confirm('请重新登录', { confirmButtonText: '重新登录', closeOnClickModal: false, showCancelButton: false, showClose: false, type: 'warning' }).then(() => { useBasicStore().resetStateAndToLogin() authorTipDoor = true }) } //请求前拦截 service.interceptors.request.use( (req: any) => { const { token, axiosPromiseArr }: any = useBasicStore() //axiosPromiseArr收集请求地址,用于取消请求 req.cancelToken = new axios.CancelToken((cancel) => { tempReqUrlSave = req.url axiosPromiseArr.push({ url: req.url, cancel }) }) //设置token到header if (token) req.headers['Authorization'] = token //如果req.method给get 请求参数设置为 ?name=xxx if ('get'.includes(req.method?.toLowerCase()) && !req.params) req.params = req.data //req loading // @ts-ignore if (req.reqLoading ?? true) { loadingInstance = ElLoading.service({ lock: true, fullscreen: true, // spinner: 'CircleCheck', text: '数据载入中...', background: 'rgba(0, 0, 0, 0.1)' }) } return req }, (err) => { //发送请求失败 Promise.reject(err) } ) //请求后拦截 service.interceptors.response.use( (res: any) => { //取消请求 useBasicStore().remotePromiseArrByReqUrl(tempReqUrlSave) if (loadingInstance) { loadingInstance && loadingInstance.close() } //download file if (res.data?.type?.includes("sheet")) { return res } const { code, msg } = res.data const successCode = [0,200,20000] const noAuthCode = [401,403] if (successCode.includes(code)) { return res.data } else { //authorTipDoor 防止多个请求 多次alter if (authorTipDoor) { if (noAuthCode.includes(code)) { noAuthDill() } else { // @ts-ignore if (!res.config?.isNotTipErrorMsg) { ElMessage.error({ message: msg, duration: 2 * 1000 }) } else { return res } return Promise.reject(msg) } } } }, //响应报错 (err) => { //取消请求 useBasicStore().remotePromiseArrByReqUrl(tempReqUrlSave) if (loadingInstance) { loadingInstance && loadingInstance.close() } ElMessage.error({ message: err, duration: 2 * 1000 }) return Promise.reject(err) } ) //导出service实例给页面调用 , config->页面的配置 export default function axiosReq(config) { return service({ baseURL: import.meta.env.VITE_APP_BASE_URL, timeout: 8000, ...config }) } ================================================ FILE: src/utils/bus.ts ================================================ //bus even import mitt from 'mitt' export default mitt() ================================================ FILE: src/utils/common-util.ts ================================================ export default { getWeek() { return `星期${'日一二三四五六'.charAt(new Date().getDay())}` // this.showDate=this.$momentMini(new Date()).format('YYYY年MM月DD日,')+str }, /* 表单验证*/ // 匹配手机 mobilePhone(str) { const reg = /^0?1[0-9]{10}$/ return reg.test(str) }, /* * 传入一串num四个 一个空格 * */ toSplitNumFor(num, numToSpace) { return num.replace(/(.{4})/g, '$1 ') }, // 匹配银行卡号 bankCardNo(str) { const reg = /^\d{15,20}$/ return reg.test(str) }, // 邮箱 regEmail(str) { const reg = /^([a-zA-Z]|[0-9])(\w|-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/ return reg.test(str) }, // 省份证 idCardNumber(str) { const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ return reg.test(str) }, /* 常用数组操作*/ /* * 删除数组中的指定元素 * arrItem 数组的index下标 * return 删除后的数组 * */ deleteArrItem(arr, arrItem) { arr.splice(arr.indexOf(arrItem), 1) }, /* * 数组去重 * arr:要去重的数组 * return 去重后的数组 * */ arrToRepeat(arr) { return arr.filter((ele, index, thisArr) => { // 因为indexOf返回元素出现的第一个index位置,如果有重复的话那么他的位置永远是第一次出现的index,这就与他本身的index不相符,则删掉. return thisArr.indexOf(ele) === index }) }, /* * 数组去重 * seriesArr: 数组 * return 去重后的数组 * */ deRepeatArr(seriesArr) { return [...new Set(seriesArr)] }, /* * 根据arrObj 删除arrObj2 根据arrObj objKey查找删除 * arrObj: 数组对象 * arrObj2: 要被删除的对象 * objKey: arrObj中对象的某一个key名称 * return: arrObj2删除过后的数组 * */ byArrObjDeleteArrObj2(arrObj, arrObj2, objKey) { arrObj .map((value) => { return value[objKey] }) .forEach((value2) => { arrObj2.splice( arrObj2.findIndex((item) => item[objKey] === value2), 1 ) }) return arrObj2 }, /* * 删除arrObj某一项 根据objKey中的key的值等于value的值 * arrObj: 数组对象 * objKey:arrObj中对象的某一个key名称 * return: arrObj删除过后的数组 * */ deleteArrObjByKey(arrObj, objKey, value) { //foreach splice //for substring slice 不改变原数组 arrObj.splice( arrObj.findIndex((item) => item[objKey] === value), 1 ) return arrObj }, /* * 查找arrObj某一项 根据objKey中的值 * arrObj: 数组对象 * objKey:arrObj中对象的某一个key名称 * return: arrObj查找 过后的数组 * */ findArrObjByKey(arrObj, objKey, value) { return arrObj[arrObj.findIndex((item) => item[objKey] == value)] }, /* * 根据arrObj 筛选arrObj2 根据arrObj objKey值查找 * arrObj: 数组对象 * arrObj2: 要被删除的对象 * objKey: arrObj中对象的某一个key名称 * return: arrObj2删除过后的数组 * */ byArrObjFindArrObj2(arrObj, arrObj2, objKey) { const arrObj3: Array = [] arrObj .map((value) => { return value[objKey] }) .forEach((value2) => { const arrIndex = arrObj2.findIndex((item) => item[objKey] === value2) if (arrIndex !== -1) { arrObj3.push(arrObj2[arrIndex]) } }) return arrObj3 } } ================================================ FILE: src/views/basic-demo/hook/index.vue ================================================ ================================================ FILE: src/views/basic-demo/keep-alive/index.vue ================================================ ================================================ FILE: src/views/basic-demo/keep-alive/second-child.vue ================================================ ================================================ FILE: src/views/basic-demo/keep-alive/second-keep-alive.vue ================================================ ================================================ FILE: src/views/basic-demo/keep-alive/tab-keep-alive.vue ================================================ ================================================ FILE: src/views/basic-demo/keep-alive/third-child.vue ================================================ ================================================ FILE: src/views/basic-demo/mock/index.vue ================================================ ================================================ FILE: src/views/basic-demo/parent-children/Children.vue ================================================ ================================================ FILE: src/views/basic-demo/parent-children/SubChildren.vue ================================================ ================================================ FILE: src/views/basic-demo/parent-children/index.vue ================================================ ================================================ FILE: src/views/basic-demo/pinia/index.vue ================================================ ================================================ FILE: src/views/basic-demo/svg-icon/index.vue ================================================ ================================================ FILE: src/views/basic-demo/vue3-template/Vue3Template.vue ================================================ ================================================ FILE: src/views/basic-demo/worker/index.vue ================================================ ================================================ FILE: src/views/dashboard/index.vue ================================================ ================================================ FILE: src/views/error-page/401.vue ================================================ ================================================ FILE: src/views/error-page/404.vue ================================================ ================================================ FILE: src/views/login/index.vue ================================================ ================================================ FILE: src/views/nested/menu1/index.vue ================================================ ================================================ FILE: src/views/nested/menu1/menu1-1/index.vue ================================================ ================================================ FILE: src/views/nested/menu1/menu1-2/index.vue ================================================ ================================================ FILE: src/views/nested/menu1/menu1-2/menu1-2-1/index.vue ================================================ ================================================ FILE: src/views/nested/menu1/menu1-2/menu1-2-2/index.vue ================================================ ================================================ FILE: src/views/nested/menu1/menu1-3/index.vue ================================================ ================================================ FILE: src/views/nested/menu2/index.vue ================================================ ================================================ FILE: src/views/redirect/index.tsx ================================================ import { defineComponent } from 'vue' export default defineComponent({ setup() { const route = useRoute() const router = useRouter() onBeforeMount(() => { const { params, query } = route const { path } = params router.replace({ path: `/${path}`, query }) }) return () =>
} }) ================================================ FILE: src/views/setting-switch/SettingSwitch.vue ================================================ ================================================ FILE: src/views/setting-switch/index.vue ================================================ ================================================ FILE: ts-out-dir/package.json ================================================ { "name": "vue3-admin-ts", "version": "2.0.0-rc2", "license": "MIT", "author": "kuanghua", "packageManager": "pnpm@7.9.0", "scripts": { "dev": "vite --mode serve-dev", "test": "vite --mode serve-test", "build:test": "vite build --mode build-test", "build": "vite build --mode build", "preview:build": "npm run build && vite preview ", "preview": "vite preview ", "lint": "eslint --ext .js,.jsx,.vue,.ts,.tsx src --fix", "prepare": "husky install", "test:unit": "vue-cli-service test:unit", "test:watchAll": "vue-cli-service test:unit --watchAll", "test:cov": "vue-cli-service test:unit --coverage", "test:majestic": "majestic", "vitest": "vitest --ui", "tsc-check": "tsc", "coverage": "vitest run --coverage" }, "dependencies": { "@element-plus/icons-vue": "^2.0.4", "axios": "0.21.3", "echarts": "5.3.2", "element-plus": "^2.2.9", "js-error-collection": "^1.0.7", "mitt": "3.0.0", "moment-mini": "2.22.1", "nprogress": "0.2.0", "path-browserify": "^1.0.1", "path-to-regexp": "^6.2.1", "pinia": "^2.0.16", "pinia-plugin-persistedstate": "2.3.0", "vue": "^3.2.37", "vue-clipboard3": "^2.0.0", "vue-router": "^4.1.5" }, "devDependencies": { "@babel/eslint-parser": "7.16.3", "@types/echarts": "4.9.7", "@types/mockjs": "1.0.6", "@types/node": "^17.0.35", "@types/path-browserify": "^1.0.0", "@typescript-eslint/eslint-plugin": "5.30.0", "@typescript-eslint/parser": "5.30.0", "@vitejs/plugin-legacy": "^2.2.0", "@vitejs/plugin-vue": "^2.3.3", "@vitejs/plugin-vue-jsx": "^2.0.1", "@vitest/coverage-c8": "^0.22.1", "@vitest/ui": "^0.22.1", "@vue/cli-plugin-unit-jest": "4.5.17", "@vue/cli-service": "4.5.17", "@vue/test-utils": "^2.0.2", "@vueuse/core": "^8.7.5", "eslint": "8.18.0", "eslint-config-prettier": "8.5.0", "eslint-define-config": "1.5.1", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.26.0", "eslint-plugin-jsonc": "^2.3.0", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-prettier": "4.1.0", "eslint-plugin-unicorn": "^43.0.2", "eslint-plugin-vue": "9.1.1", "husky": "7.0.2", "jsdom": "16.4.0", "jsonc-eslint-parser": "^2.1.0", "majestic": "1.8.1", "mockjs": "1.1.0", "prettier": "2.2.1", "resize-observer-polyfill": "^1.5.1", "rollup-plugin-visualizer": "^5.8.3", "sass": "^1.52.1", "svg-sprite-loader": "6.0.11", "typescript": "^4.7.2", "unocss": "^0.33.5", "unplugin-auto-import": "^0.11.2", "unplugin-vue-components": "^0.22.8", "unplugin-vue-define-options": "^0.12.2", "vite": "^3.1.8", "vite-plugin-html": "^3.2.0", "vite-plugin-mkcert": "^1.7.2", "vite-plugin-mock": "^2.9.6", "vite-plugin-svg-icons": "^2.0.1", "vitest": "^0.22.1", "vue-tsc": "^0.34.16" }, "pnpm": { "peerDependencyRules": { "ignoreMissing": [ "html-webpack-plugin", "vite-plugin-mock", "unplugin-auto-import", "unplugin-vue-components", "vue-template-compiler", "unocss", "unplugin", "vite-plugin-mock", "@vitejs/plugin-legacy", "@vitejs/plugin-vue", "@vitejs/*", "@babel/*", "vite", "vue", "@unocss/vite", "rollup", "vue-jest", "@babel/*" ] } }, "browserslist": [ "> 1%", "not ie 11", "not op_mini all" ], "engines": { "node": ">= 16 <18", "pnpm": ">= 6 <8" } } ================================================ FILE: ts-out-dir/src/api/user.d.ts ================================================ export declare const userInfoReq: () => Promise; export declare const loginReq: (subForm: any) => import("axios").AxiosPromise; export declare const loginOutReq: () => import("axios").AxiosPromise; ================================================ FILE: ts-out-dir/src/api/user.js ================================================ import axiosReq from '@/utils/axios-req'; export const userInfoReq = () => { return new Promise((resolve) => { const reqConfig = { url: '/basis-func/user/getUserInfo', params: { plateFormId: 2 }, method: 'post' }; axiosReq(reqConfig).then(({ data }) => { resolve(data); }); }); }; export const loginReq = (subForm) => { return axiosReq({ url: '/basis-func/user/loginValid', params: subForm, method: 'post' }); }; export const loginOutReq = () => { return axiosReq({ url: '/basis-func/user/loginValid', method: 'post' }); }; ================================================ FILE: ts-out-dir/src/directives/button-codes.d.ts ================================================ declare const _default: { mounted(el: any, binding: any): void; componentUpdated(el: any, binding: any): void; }; export default _default; ================================================ FILE: ts-out-dir/src/directives/button-codes.js ================================================ import { useBasicStore } from '@/store/basic'; function checkPermission(el, { value }) { if (value && Array.isArray(value)) { if (value.length) { const permissionRoles = value; const hasPermission = useBasicStore().buttonCodes?.some((code) => permissionRoles.includes(code)); if (!hasPermission) el.parentNode && el.parentNode.removeChild(el); } } else { throw new Error(`need roles! Like v-permission="['admin','editor']"`); } } export default { mounted(el, binding) { checkPermission(el, binding); }, componentUpdated(el, binding) { checkPermission(el, binding); } }; ================================================ FILE: ts-out-dir/src/directives/codes-permission.d.ts ================================================ declare const _default: { mounted(el: any, binding: any): void; componentUpdated(el: any, binding: any): void; }; export default _default; ================================================ FILE: ts-out-dir/src/directives/codes-permission.js ================================================ import { useBasicStore } from '@/store/basic'; function checkPermission(el, { value }) { if (value && Array.isArray(value)) { if (value.length > 0) { const permissionRoles = value; const hasPermission = useBasicStore().codes?.some((role) => permissionRoles.includes(role)); if (!hasPermission) el.parentNode && el.parentNode.removeChild(el); } } else { throw new Error(`need codes! Like v-codes-permission="['admin','editor']"`); } } export default { mounted(el, binding) { checkPermission(el, binding); }, componentUpdated(el, binding) { checkPermission(el, binding); } }; ================================================ FILE: ts-out-dir/src/directives/index.d.ts ================================================ export default function (app: any): void; ================================================ FILE: ts-out-dir/src/directives/index.js ================================================ import buttonCodes from './button-codes'; import codesPermission from './codes-permission'; import rolesPermission from './roles-permission'; export default function (app) { app.directive('ButtonCodes', buttonCodes); app.directive('CodesPermission', codesPermission); app.directive('RolesPermission', rolesPermission); } ================================================ FILE: ts-out-dir/src/directives/roles-permission.d.ts ================================================ declare const _default: { mounted(el: any, binding: any): void; componentUpdated(el: any, binding: any): void; }; export default _default; ================================================ FILE: ts-out-dir/src/directives/roles-permission.js ================================================ import { useBasicStore } from '@/store/basic'; function checkPermission(el, { value }) { if (value && Array.isArray(value)) { if (value.length > 0) { const permissionRoles = value; const hasPermission = useBasicStore().roles?.some((role) => permissionRoles.includes(role)); if (!hasPermission) el.parentNode && el.parentNode.removeChild(el); } } else { throw new Error(`need roles! Like v-roles-permission="['admin','editor']"`); } } export default { mounted(el, binding) { checkPermission(el, binding); }, componentUpdated(el, binding) { checkPermission(el, binding); } }; ================================================ FILE: ts-out-dir/src/hooks/use-common.d.ts ================================================ export declare const sleepTimeout: (time: number) => Promise; export declare const useCommon: () => { totalPage: import("vue").Ref; startEndArr: import("vue").Ref; searchForm: import("vue").Ref<{}>; dialogTitle: import("vue").Ref; detailDialog: import("vue").Ref; }; export declare function cloneDeep(value: any): any; export declare const copyValueToClipboard: (value: any) => void; ================================================ FILE: ts-out-dir/src/hooks/use-common.js ================================================ import { reactive, toRefs } from 'vue'; import useClipboard from 'vue-clipboard3'; import { ElMessage } from 'element-plus'; export const sleepTimeout = (time) => { return new Promise((resolve) => { const timer = setTimeout(() => { clearTimeout(timer); resolve(null); }, time); }); }; export const useCommon = () => { const state = reactive({ totalPage: 0, startEndArr: [], searchForm: {}, dialogTitle: '', detailDialog: false }); return { ...toRefs(state) }; }; export function cloneDeep(value) { return JSON.parse(JSON.stringify(value)); } const { toClipboard } = useClipboard(); export const copyValueToClipboard = (value) => { toClipboard(JSON.stringify(value)); ElMessage.success('复制成功'); }; ================================================ FILE: ts-out-dir/src/hooks/use-element.d.ts ================================================ import type { EpPropMergeType } from 'element-plus/es/utils'; export declare const useElement: () => { tableData: import("vue").Ref; rowDeleteIdArr: import("vue").Ref; loadingId: import("vue").Ref; formModel: import("vue").Ref<{}>; subForm: import("vue").Ref<{}>; searchForm: import("vue").Ref<{}>; formRules: import("vue").Ref<{ isNull: (msg: string) => { required: boolean; message: string; trigger: string; }[]; isNotNull: (msg: string) => { required: boolean; message: string; trigger: string; }[]; upZeroInt: (msg: string) => { required: boolean; validator: (rule: any, value: any, callback: any) => void; trigger: string; }[]; zeroInt: (msg: string) => { required: boolean; validator: (rule: any, value: any, callback: any) => void; trigger: string; }[]; money: (msg: string) => { required: boolean; validator: (rule: any, value: any, callback: any) => void; trigger: string; }[]; phone: (msg: string) => { required: boolean; validator: (rule: any, value: any, callback: any) => void; trigger: string; }[]; email: (msg: string) => { required: boolean; validator: (rule: any, value: any, callback: any) => void; trigger: string; }[]; }>; datePickerOptions: import("vue").Ref<{ disabledDate: (time: any) => boolean; }>; startEndArr: import("vue").Ref; dialogTitle: import("vue").Ref; detailDialog: import("vue").Ref; isDialogEdit: import("vue").Ref; dialogVisible: import("vue").Ref; tableLoading: import("vue").Ref; treeData: import("vue").Ref; defaultProps: import("vue").Ref<{ children: string; label: string; }>; }; export declare const elMessage: (message: string, type?: any) => void; export declare const elLoading: () => void; export declare const closeLoading: () => void; export declare const elNotify: (message: string, type: EpPropMergeType | undefined, title: string, duration: number) => void; export declare const elConfirmNoCancelBtn: (title: string, message: string) => Promise; export declare const elConfirm: (title: string, message: string) => Promise; export declare const casHandleChange: () => void; ================================================ FILE: ts-out-dir/src/hooks/use-element.js ================================================ import { reactive, ref, toRefs } from 'vue'; import { ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-plus'; export const useElement = () => { const upZeroInt = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)); } if (/^\+?[1-9]\d*$/.test(value)) { callback(); } else { callback(new Error(`${msg}输入有误`)); } }; const zeroInt = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)); } if (/^\+?[0-9]\d*$/.test(value)) { callback(); } else { callback(new Error(`${msg}输入有误`)); } }; const money = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)); } if (/((^[1-9]\d*)|^0)(\.\d{0,2}){0,1}$/.test(value)) { callback(); } else { callback(new Error(`${msg}输入有误`)); } }; const phone = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)); } if (/^0?1[0-9]{10}$/.test(value)) { callback(); } else { callback(new Error(`${msg}输入有误`)); } }; const email = (rule, value, callback, msg) => { if (!value) { callback(new Error(`${msg}不能为空`)); } if (/(^([a-zA-Z]|[0-9])(\w|-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4}))$/.test(value)) { callback(); } else { callback(new Error(`${msg}`)); } }; const state = reactive({ tableData: [], rowDeleteIdArr: [], loadingId: null, formModel: {}, subForm: {}, searchForm: {}, formRules: { isNull: (msg) => [{ required: false, message: `${msg}`, trigger: 'blur' }], isNotNull: (msg) => [{ required: true, message: `${msg}`, trigger: 'blur' }], upZeroInt: (msg) => [ { required: true, validator: (rule, value, callback) => upZeroInt(rule, value, callback, msg), trigger: 'blur' } ], zeroInt: (msg) => [ { required: true, validator: (rule, value, callback) => zeroInt(rule, value, callback, msg), trigger: 'blur' } ], money: (msg) => [ { required: true, validator: (rule, value, callback) => money(rule, value, callback, msg), trigger: 'blur' } ], phone: (msg) => [ { required: true, validator: (rule, value, callback) => phone(rule, value, callback, msg), trigger: 'blur' } ], email: (msg) => [ { required: true, validator: (rule, value, callback) => email(rule, value, callback, msg), trigger: 'blur' } ] }, datePickerOptions: { disabledDate: (time) => { return time.getTime() < Date.now() - 86400000; } }, startEndArr: [], dialogTitle: '添加', detailDialog: false, isDialogEdit: false, dialogVisible: false, tableLoading: false, treeData: [], defaultProps: { children: 'children', label: 'label' } }); return { ...toRefs(state) }; }; export const elMessage = (message, type) => { ElMessage({ showClose: true, message: message || '成功', type: type || 'success', center: false }); }; let loadingId = null; export const elLoading = () => { loadingId = ElLoading.service({ lock: true, text: '数据载入中', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.1)' }); }; export const closeLoading = () => { loadingId.close(); }; export const elNotify = (message, type, title, duration) => { ElNotification({ title: title || '提示', type: type || 'success', message: message || '请传入提示消息', position: 'top-right', duration: duration || 2500, offset: 40 }); }; export const elConfirmNoCancelBtn = (title, message) => { return ElMessageBox({ message: message || '你确定要删除吗', title: title || '确认框', confirmButtonText: '确定', cancelButtonText: '取消', showCancelButton: false, type: 'warning' }); }; export const elConfirm = (title, message) => { return ElMessageBox({ message: message || '你确定要删除吗', title: title || '确认框', confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }); }; const cascaderKey = ref(); export const casHandleChange = () => { cascaderKey.value += cascaderKey.value; }; ================================================ FILE: ts-out-dir/src/hooks/use-error-log.d.ts ================================================ export declare const useErrorLog: () => void; ================================================ FILE: ts-out-dir/src/hooks/use-error-log.js ================================================ import { jsErrorCollection } from 'js-error-collection'; import pack from '../../package.json'; import settings from '@/settings'; import bus from '@/utils/bus'; import axiosReq from '@/utils/axios-req'; const reqUrl = '/integration-front/errorCollection/insert'; const errorLogReq = (errLog) => { axiosReq({ url: reqUrl, data: { pageUrl: window.location.href, errorLog: errLog, browserType: navigator.userAgent, version: pack.version }, method: 'post' }).then(() => { bus.emit('reloadErrorPage', {}); }); }; export const useErrorLog = () => { if (settings.errorLog?.includes(import.meta.env.VITE_APP_ENV)) { jsErrorCollection({ runtimeError: true, rejectError: true, consoleError: true }, (errLog) => { if (!errLog.includes(reqUrl)) errorLogReq(errLog); }); } }; ================================================ FILE: ts-out-dir/src/hooks/use-layout.d.ts ================================================ export declare function isExternal(path: any): boolean; export declare function resizeHandler(): void; ================================================ FILE: ts-out-dir/src/hooks/use-layout.js ================================================ import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue'; import { useBasicStore } from '@/store/basic'; export function isExternal(path) { return /^(https?:|mailto:|tel:)/.test(path); } export function resizeHandler() { const { body } = document; const WIDTH = 992; const basicStore = useBasicStore(); const isMobile = () => { const rect = body.getBoundingClientRect(); return rect.width - 1 < WIDTH; }; const resizeHandler = () => { if (!document.hidden) { if (isMobile()) { basicStore.setSidebarOpen(false); } else { basicStore.setSidebarOpen(true); } } }; onBeforeMount(() => { window.addEventListener('resize', resizeHandler); }); onMounted(() => { if (isMobile()) { basicStore.setSidebarOpen(false); } else { basicStore.setSidebarOpen(true); } }); onBeforeUnmount(() => { window.removeEventListener('resize', resizeHandler); }); } ================================================ FILE: ts-out-dir/src/hooks/use-permission.d.ts ================================================ import type { RouterTypes } from '~/basic'; import 'nprogress/nprogress.css'; export declare const filterAsyncRoutesByMenuList: (menuList: any) => RouterTypes; export declare function filterAsyncRoutesByRoles(routes: any, roles: any): RouterTypes; export declare function filterAsyncRouterByCodes(codesRoutes: any, codes: any): RouterTypes; export declare function filterAsyncRouter({ menuList, roles, codes }: { menuList: any; roles: any; codes: any; }): void; export declare function resetRouter(): void; export declare function resetState(): void; export declare function freshRouter(data: any): void; export declare const progressStart: () => void; export declare const progressClose: () => void; ================================================ FILE: ts-out-dir/src/hooks/use-permission.js ================================================ import NProgress from 'nprogress'; import Layout from '@/layout/index.vue'; import router, { asyncRoutes, constantRoutes, roleCodeRoutes } from '@/router'; import 'nprogress/nprogress.css'; import { useBasicStore } from '@/store/basic'; const buttonCodes = []; export const filterAsyncRoutesByMenuList = (menuList) => { const filterRouter = []; menuList.forEach((route) => { if (route.category === 3) { buttonCodes.push(route.code); } else { const itemFromReqRouter = getRouteItemFromReqRouter(route); if (route.children?.length) { itemFromReqRouter.children = filterAsyncRoutesByMenuList(route.children); } filterRouter.push(itemFromReqRouter); } }); return filterRouter; }; const getRouteItemFromReqRouter = (route) => { const tmp = { meta: { title: '' } }; const routeKeyArr = ['path', 'component', 'redirect', 'alwaysShow', 'name', 'hidden']; const metaKeyArr = ['title', 'activeMenu', 'elSvgIcon', 'icon']; const modules = import.meta.glob('../views/**/**.vue'); routeKeyArr.forEach((fItem) => { if (fItem === 'component') { if (route[fItem] === 'Layout') { tmp[fItem] = Layout; } else { tmp[fItem] = modules[`../views/${route[fItem]}`]; } } else if (fItem === 'path' && route.parentId === 0) { tmp[fItem] = `/${route[fItem]}`; } else if (['hidden', 'alwaysShow'].includes(fItem)) { tmp[fItem] = !!route[fItem]; } else if (['name'].includes(fItem)) { tmp[fItem] = route['code']; } else if (route[fItem]) { tmp[fItem] = route[fItem]; } }); metaKeyArr.forEach((fItem) => { if (route[fItem] && tmp.meta) tmp.meta[fItem] = route[fItem]; }); if (route.extra) { Object.entries(route.extra.parse(route.extra)).forEach(([key, value]) => { if (key === 'meta' && tmp.meta) { tmp.meta[key] = value; } else { tmp[key] = value; } }); } return tmp; }; export function filterAsyncRoutesByRoles(routes, roles) { const res = []; routes.forEach((route) => { const tmp = { ...route }; if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutesByRoles(tmp.children, roles); } res.push(tmp); } }); return res; } function hasPermission(roles, route) { if (route?.meta?.roles) { return roles?.some((role) => route.meta.roles.includes(role)); } else { return true; } } export function filterAsyncRouterByCodes(codesRoutes, codes) { const filterRouter = []; codesRoutes.forEach((routeItem) => { if (hasCodePermission(codes, routeItem)) { if (routeItem.children) routeItem.children = filterAsyncRouterByCodes(routeItem.children, codes); filterRouter.push(routeItem); } }); return filterRouter; } function hasCodePermission(codes, routeItem) { if (routeItem.meta?.code) { return codes.includes(routeItem.meta.code) || routeItem.hidden; } else { return true; } } export function filterAsyncRouter({ menuList, roles, codes }) { const basicStore = useBasicStore(); let accessRoutes = []; const permissionMode = basicStore.settings?.permissionMode; if (permissionMode === 'rbac') { accessRoutes = filterAsyncRoutesByMenuList(menuList); } else if (permissionMode === 'roles') { accessRoutes = filterAsyncRoutesByRoles(roleCodeRoutes, roles); } else { accessRoutes = filterAsyncRouterByCodes(roleCodeRoutes, codes); } accessRoutes.forEach((route) => router.addRoute(route)); asyncRoutes.forEach((item) => router.addRoute(item)); basicStore.setFilterAsyncRoutes(accessRoutes); } export function resetRouter() { const routeNameSet = new Set(); router.getRoutes().forEach((fItem) => { if (fItem.name) routeNameSet.add(fItem.name); }); routeNameSet.forEach((setItem) => router.removeRoute(setItem)); constantRoutes.forEach((feItem) => router.addRoute(feItem)); } export function resetState() { resetRouter(); useBasicStore().resetState(); } export function freshRouter(data) { resetRouter(); filterAsyncRouter(data); } NProgress.configure({ showSpinner: false }); export const progressStart = () => { NProgress.start(); }; export const progressClose = () => { NProgress.done(); }; ================================================ FILE: ts-out-dir/src/hooks/use-self-router.d.ts ================================================ export declare const getQueryParam: () => any; export declare const routerPush: (name: any, params: any) => void; export declare const routerReplace: (name: any, params: any) => void; export declare const routerBack: () => void; ================================================ FILE: ts-out-dir/src/hooks/use-self-router.js ================================================ import router from '@/router'; export const getQueryParam = () => { const route = router.currentRoute; if (route.value?.query.params) { return JSON.parse(route.value.query.params); } }; export const routerPush = (name, params) => { let data = {}; if (params) { data = { params: JSON.stringify(params) }; } else { data = {}; } router.push({ name, query: data }); }; export const routerReplace = (name, params) => { let data = {}; if (params) { data = { params: JSON.stringify(params) }; } else { data = {}; } router.replace({ name, query: data }); }; export const routerBack = () => { router.go(-1); }; ================================================ FILE: ts-out-dir/src/hooks/use-table.d.ts ================================================ export declare const useTable: (searchForm: any, selectPageReq: any) => { pageNum: import("vue").Ref; pageSize: import("vue").Ref; totalPage: import("vue").Ref; tableListData: import("vue").Ref; tableListReq: (config: any) => import("axios").AxiosPromise; dateRangePacking: (timeArr: any) => void; multipleSelection: import("vue").Ref; handleSelectionChange: (val: any) => void; handleCurrentChange: (val: any) => void; handleSizeChange: (val: any) => void; resetPageReq: () => void; multiDelBtnDill: (reqConfig: any) => void; tableDelDill: (row: any, reqConfig: any) => void; }; ================================================ FILE: ts-out-dir/src/hooks/use-table.js ================================================ import { ref } from 'vue'; import momentMini from 'moment-mini'; import { elConfirm, elMessage } from './use-element'; export const useTable = (searchForm, selectPageReq) => { const tableListData = ref([]); const totalPage = ref(0); const pageNum = ref(1); const pageSize = ref(20); const tableListReq = (config) => { const data = Object.assign({ pageNum: pageNum.value, pageSize: pageSize.value }, JSON.parse(JSON.stringify(searchForm))); Object.keys(data).forEach((fItem) => { if (['', null, undefined, Number.NaN].includes(data[fItem])) delete data[fItem]; if (config.method === 'get') { if (Array.isArray(data[fItem])) delete data[fItem]; if (data[fItem] instanceof Object) delete data[fItem]; } }); const reqConfig = { data, ...config }; return axiosReq(reqConfig); }; const dateRangePacking = (timeArr) => { if (timeArr && timeArr.length === 2) { searchForm.startTime = timeArr[0]; if (searchForm.endTime) { searchForm.endTime = momentMini(timeArr[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss'); } } else { searchForm.startTime = ''; searchForm.endTime = ''; } }; const handleCurrentChange = (val) => { pageNum.value = val; selectPageReq(); }; const handleSizeChange = (val) => { pageSize.value = val; selectPageReq(); }; const resetPageReq = () => { pageNum.value = 1; selectPageReq(); }; const multipleSelection = ref([]); const handleSelectionChange = (val) => { multipleSelection.value = val; }; const multiDelBtnDill = (reqConfig) => { let rowDeleteIdArr = []; let deleteNameTitle = ''; rowDeleteIdArr = multipleSelection.value.map((mItem) => { deleteNameTitle = `${deleteNameTitle + mItem.id},`; return mItem.id; }); if (rowDeleteIdArr.length === 0) { elMessage('表格选项不能为空', 'warning'); return; } const stringLength = deleteNameTitle.length - 1; elConfirm('删除', `您确定要删除【${deleteNameTitle.slice(0, stringLength)}】吗`).then(() => { const data = rowDeleteIdArr; axiosReq({ data, method: 'DELETE', bfLoading: true, ...reqConfig }).then(() => { elMessage('删除成功'); resetPageReq(); }); }); }; const tableDelDill = (row, reqConfig) => { elConfirm('确定', `您确定要删除【${row.id}】吗?`).then(() => { axiosReq(reqConfig).then(() => { resetPageReq(); elMessage(`【${row.id}】删除成功`); }); }); }; return { pageNum, pageSize, totalPage, tableListData, tableListReq, dateRangePacking, multipleSelection, handleSelectionChange, handleCurrentChange, handleSizeChange, resetPageReq, multiDelBtnDill, tableDelDill }; }; ================================================ FILE: ts-out-dir/src/lib/element-plus.d.ts ================================================ export default function (app: any): void; ================================================ FILE: ts-out-dir/src/lib/element-plus.js ================================================ import * as AllComponent from 'element-plus'; const elementPlusComponentNameArr = ['ElButton']; export default function (app) { elementPlusComponentNameArr.forEach((component) => { app.component(component, AllComponent[component]); }); } ================================================ FILE: ts-out-dir/src/main.d.ts ================================================ import '@/styles/index.scss'; import 'virtual:svg-icons-register'; import './permission'; import './theme/index.scss'; import 'uno.css'; import 'element-plus/dist/index.css'; ================================================ FILE: ts-out-dir/src/main.js ================================================ import { createApp } from 'vue'; import App from './App.vue'; const app = createApp(App); import router from './router'; import '@/styles/index.scss'; import 'virtual:svg-icons-register'; import svgIcon from '@/icons/SvgIcon.vue'; import directive from '@/directives'; import './permission'; import './theme/index.scss'; import 'uno.css'; import ElementPlus from 'element-plus'; import 'element-plus/dist/index.css'; app.use(ElementPlus); app.component('SvgIcon', svgIcon); directive(app); import { createPinia } from 'pinia'; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; const pinia = createPinia(); pinia.use(piniaPluginPersistedstate); app.use(pinia); app.use(router).mount('#app'); ================================================ FILE: ts-out-dir/src/permission.d.ts ================================================ export {}; ================================================ FILE: ts-out-dir/src/permission.js ================================================ import router from '@/router'; import { filterAsyncRouter, progressClose, progressStart } from '@/hooks/use-permission'; import { useBasicStore } from '@/store/basic'; import { userInfoReq } from '@/api/system.ts'; const whiteList = ['/login', '/404', '/401']; router.beforeEach(async (to) => { progressStart(); const basicStore = useBasicStore(); if (basicStore.token) { if (to.path === '/login') { return '/'; } else { if (!basicStore.getUserInfo) { try { const userData = await userInfoReq(); filterAsyncRouter(userData); basicStore.setUserInfo(userData); return { ...to, replace: true }; } catch (e) { console.error(`route permission error${e}`); basicStore.resetState(); progressClose(); return `/login?redirect=${to.path}`; } } else { return true; } } } else { if (!whiteList.includes(to.path)) { return `/login?redirect=${to.path}`; } else { return true; } } }); router.afterEach(() => { progressClose(); }); ================================================ FILE: ts-out-dir/src/router/index.d.ts ================================================ import type { RouterTypes } from '~/basic'; export declare const constantRoutes: RouterTypes; export declare const roleCodeRoutes: RouterTypes; export declare const asyncRoutes: RouterTypes; declare const router: import("vue-router").Router; export default router; ================================================ FILE: ts-out-dir/src/router/index.js ================================================ import { createRouter, createWebHashHistory } from 'vue-router'; import Layout from '@/layout/index.vue'; export const constantRoutes = [ { path: '/redirect', component: Layout, hidden: true, children: [ { path: '/redirect/:path(.*)', component: () => import('@/views/redirect') } ] }, { path: '/login', component: () => import('@/views/login/index.vue'), hidden: true }, { path: '/404', component: () => import('@/views/error-page/404.vue'), hidden: true }, { path: '/401', component: () => import('@/views/error-page/401.vue'), hidden: true }, { path: '/', component: Layout, redirect: '/dashboard', children: [ { path: 'dashboard', name: 'Dashboard', component: () => import('@/views/dashboard/index.vue'), meta: { title: 'Dashboard', elSvgIcon: 'Fold' } } ] }, { path: '/setting-switch', component: Layout, children: [ { path: 'index', component: () => import('@/views/setting-switch/index.vue'), name: 'SettingSwitch', meta: { title: 'Setting Switch', icon: 'example', affix: true } } ] }, { path: '/error-collection', component: Layout, meta: { title: 'Error Collection', icon: 'eye' }, alwaysShow: true, children: [ { path: 'error-collection-table-query', component: () => import('@/views/error-collection/ErrorCollectionTableQuery.vue'), name: 'ErrorCollectionTableQuery', meta: { title: 'Index' } }, { path: 'error-log-test', component: () => import('@/views/error-log/ErrorLogTest.vue'), name: 'ErrorLogTest', meta: { title: 'ErrorLog Test' } } ] }, { path: '/nested', component: Layout, redirect: '/nested/menu1', name: 'Nested', meta: { title: 'Nested', icon: 'nested' }, children: [ { path: 'menu1', component: () => import('@/views/nested/menu1/index.vue'), name: 'Menu1', meta: { title: 'Menu1' }, children: [ { path: 'menu1-1', component: () => import('@/views/nested/menu1/menu1-1/index.vue'), name: 'Menu1-1', meta: { title: 'Menu1-1' } }, { path: 'menu1-2', component: () => import('@/views/nested/menu1/menu1-2/index.vue'), name: 'Menu1-2', meta: { title: 'Menu1-2' }, children: [ { path: 'menu1-2-1', component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1/index.vue'), name: 'Menu1-2-1', meta: { title: 'Menu1-2-1' } }, { path: 'menu1-2-2', component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2/index.vue'), name: 'Menu1-2-2', meta: { title: 'Menu1-2-2' } } ] }, { path: 'menu1-3', component: () => import('@/views/nested/menu1/menu1-3/index.vue'), name: 'Menu1-3', meta: { title: 'Menu1-3' } } ] }, { path: 'menu2', component: () => import('@/views/nested/menu2/index.vue'), name: 'Menu2', meta: { title: 'menu2' } } ] }, { path: '/external-link', component: Layout, children: [ { component: () => { }, path: 'https://github.com/jzfai/vue3-admin-ts.git', meta: { title: 'External Link', icon: 'link' } } ] } ]; export const roleCodeRoutes = [ { path: '/roles-codes', component: Layout, redirect: '/roles-codes/page', alwaysShow: true, name: 'Permission', meta: { title: 'Permission', icon: 'lock', roles: ['admin', 'editor'] }, children: [ { path: 'index', component: () => import('@/views/roles-codes/index.vue'), name: 'RolesCodes', meta: { title: 'index' } }, { path: 'roleIndex', component: () => import('@/views/roles-codes/role-index.vue'), name: 'RoleIndex', meta: { title: 'Role Index', roles: ['admin'] } }, { path: 'code-index', component: () => import('@/views/roles-codes/code-index.vue'), name: 'CodeIndex', meta: { title: 'Code Index', code: 16 } }, { path: 'button-permission', component: () => import('@/views/roles-codes/button-permission.vue'), name: 'ButtonPermission', meta: { title: 'Button Permission' } } ] } ]; export const asyncRoutes = [ { path: '/:catchAll(.*)', name: 'CatchAll', redirect: '/404', hidden: true } ]; const router = createRouter({ history: createWebHashHistory(), scrollBehavior: () => ({ top: 0 }), routes: constantRoutes }); export default router; ================================================ FILE: ts-out-dir/src/settings.d.ts ================================================ import type { SettingsConfig } from '~/basic'; declare const settings: SettingsConfig; export default settings; ================================================ FILE: ts-out-dir/src/settings.js ================================================ const settings = { title: 'Vue3 Admin Template', sidebarLogo: true, showNavbarTitle: false, ShowDropDown: true, showHamburger: true, showLeftMenu: true, showTagsView: true, tagsViewNum: 6, showTopNavbar: true, mainNeedAnimation: true, isNeedNprogress: true, isNeedLogin: true, permissionMode: 'roles', openProdMock: true, errorLog: ['prod'], delWindowHeight: '210px', tmpToken: 'tmp_token', viteBasePath: './' }; export default settings; ================================================ FILE: ts-out-dir/src/store/basic.d.ts ================================================ import type { RouterTypes } from '~/basic'; export declare const useBasicStore: import("pinia").StoreDefinition<"basic", { token: string; getUserInfo: boolean; userInfo: { username: string; avatar: string; }; allRoutes: RouterTypes; buttonCodes: never[]; filterAsyncRoutes: never[]; roles: string[]; codes: number[]; cachedViews: string[]; cachedViewsDeep: string[]; sidebar: { opened: boolean; }; axiosPromiseArr: ObjKeys[]; settings: import("~/basic").SettingsConfig; }, {}, { setToken(data: any): void; setFilterAsyncRoutes(routes: any): void; setUserInfo({ userInfo, roles, codes }: { userInfo: any; roles: any; codes: any; }): void; resetState(): void; resetStateAndToLogin(): void; M_settings(data: any): void; setSidebarOpen(data: any): void; setToggleSideBar(): void; addCachedView(view: any): void; delCachedView(view: any): void; M_RESET_CACHED_VIEW(): void; addCachedViewDeep(view: any): void; setCacheViewDeep(view: any): void; M_RESET_CACHED_VIEW_DEEP(): void; A_sidebar_opened(data: any): void; }>; ================================================ FILE: ts-out-dir/src/store/basic.js ================================================ import { nextTick } from 'vue'; import { defineStore } from 'pinia'; import defaultSettings from '@/settings'; import router, { constantRoutes } from '@/router'; export const useBasicStore = defineStore('basic', { state: () => { return { token: '', getUserInfo: false, userInfo: { username: '', avatar: '' }, allRoutes: [], buttonCodes: [], filterAsyncRoutes: [], roles: [], codes: [], cachedViews: [], cachedViewsDeep: [], sidebar: { opened: true }, axiosPromiseArr: [], settings: defaultSettings }; }, persist: { storage: localStorage, paths: ['token'] }, actions: { setToken(data) { this.token = data; }, setFilterAsyncRoutes(routes) { this.$patch((state) => { state.filterAsyncRoutes = routes; state.allRoutes = constantRoutes.concat(routes); }); }, setUserInfo({ userInfo, roles, codes }) { const { username, avatar } = userInfo; this.$patch((state) => { state.roles = roles; state.codes = codes; state.getUserInfo = true; state.userInfo.username = username; state.userInfo.avatar = avatar; }); }, resetState() { this.$patch((state) => { state.token = ''; state.roles = []; state.codes = []; state.allRoutes = []; state.buttonCodes = []; state.filterAsyncRoutes = []; state.userInfo.username = ''; state.userInfo.avatar = ''; }); this.getUserInfo = false; }, resetStateAndToLogin() { this.resetState(); nextTick(() => { router.push({ path: '/login' }); }); }, M_settings(data) { this.$patch((state) => { state.settings = { ...state.settings, ...data }; }); }, setSidebarOpen(data) { this.$patch((state) => { state.sidebar.opened = data; }); }, setToggleSideBar() { this.$patch((state) => { state.sidebar.opened = !state.sidebar.opened; }); }, addCachedView(view) { this.$patch((state) => { if (state.cachedViews.includes(view)) return; state.cachedViews.push(view); }); }, delCachedView(view) { this.$patch((state) => { const index = state.cachedViews.indexOf(view); index > -1 && state.cachedViews.splice(index, 1); }); }, M_RESET_CACHED_VIEW() { this.$patch((state) => { state.cachedViews = []; }); }, addCachedViewDeep(view) { this.$patch((state) => { if (state.cachedViewsDeep.includes(view)) return; state.cachedViewsDeep.push(view); }); }, setCacheViewDeep(view) { this.$patch((state) => { const index = state.cachedViewsDeep.indexOf(view); index > -1 && state.cachedViewsDeep.splice(index, 1); }); }, M_RESET_CACHED_VIEW_DEEP() { this.$patch((state) => { state.cachedViewsDeep = []; }); }, A_sidebar_opened(data) { this.setSidebarOpen(data); } } }); ================================================ FILE: ts-out-dir/src/store/tagsView.d.ts ================================================ export declare const useTagsViewStore: import("pinia").StoreDefinition<"tagsView", { visitedViews: never[]; }, {}, { addVisitedView(view: any): void; delVisitedView(view: any): Promise; delOthersVisitedViews(view: any): Promise; delAllVisitedViews(): Promise; }>; ================================================ FILE: ts-out-dir/src/store/tagsView.js ================================================ import { defineStore } from 'pinia'; import setting from '@/settings'; export const useTagsViewStore = defineStore('tagsView', { state: () => { return { visitedViews: [] }; }, actions: { addVisitedView(view) { this.$patch((state) => { if (state.visitedViews.some((v) => v.path === view.path)) return; if (state.visitedViews.length >= setting.tagsViewNum) { state.visitedViews.pop(); state.visitedViews.push(Object.assign({}, view, { title: view.meta.title || 'no-name' })); } else { state.visitedViews.push(Object.assign({}, view, { title: view.meta.title || 'no-name' })); } }); }, delVisitedView(view) { return new Promise((resolve) => { this.$patch((state) => { for (const [i, v] of state.visitedViews.entries()) { if (v.path === view.path) { state.visitedViews.splice(i, 1); break; } } resolve([...state.visitedViews]); }); }); }, delOthersVisitedViews(view) { return new Promise((resolve) => { this.$patch((state) => { state.visitedViews = state.visitedViews.filter((v) => { return v.meta.affix || v.path === view.path; }); resolve([...state.visitedViews]); }); }); }, delAllVisitedViews() { return new Promise((resolve) => { this.$patch((state) => { state.visitedViews = state.visitedViews.filter((tag) => tag.meta?.affix); resolve([...state.visitedViews]); }); }); } } }); ================================================ FILE: ts-out-dir/src/utils/axios-req.d.ts ================================================ export default function axiosReq(config: any): import("axios").AxiosPromise; ================================================ FILE: ts-out-dir/src/utils/axios-req.js ================================================ import axios from 'axios'; import { ElMessage, ElMessageBox } from 'element-plus'; import { useBasicStore } from '@/store/basic'; const service = axios.create(); service.interceptors.request.use((req) => { const { token, axiosPromiseArr } = useBasicStore(); req.cancelToken = new axios.CancelToken((cancel) => { axiosPromiseArr.push({ url: req.url, cancel }); }); req.headers['AUTHORIZE_TOKEN'] = token; if ('get'.includes(req.method?.toLowerCase())) req.params = req.data; return req; }, (err) => { Promise.reject(err); }); service.interceptors.response.use((res) => { const { code } = res.data; const successCode = '0,200,20000'; const noAuthCode = '401,403'; if (successCode.includes(code)) { return res.data; } else { if (noAuthCode.includes(code) && !location.href.includes('/login')) { ElMessageBox.confirm('请重新登录', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { useBasicStore().resetStateAndToLogin(); }); } return Promise.reject(res.data); } }, (err) => { ElMessage.error({ message: err, duration: 2 * 1000 }); return Promise.reject(err); }); export default function axiosReq(config) { return service({ baseURL: import.meta.env.VITE_APP_BASE_URL, timeout: 8000, ...config }); } ================================================ FILE: ts-out-dir/src/utils/bus.d.ts ================================================ declare const _default: import("mitt").Emitter>; export default _default; ================================================ FILE: ts-out-dir/src/utils/bus.js ================================================ import mitt from 'mitt'; export default mitt(); ================================================ FILE: ts-out-dir/src/utils/common-util.d.ts ================================================ declare const _default: { getWeek(): string; mobilePhone(str: any): boolean; toSplitNumFor(num: any, numToSpace: any): any; bankCardNo(str: any): boolean; regEmail(str: any): boolean; idCardNumber(str: any): boolean; deleteArrItem(arr: any, arrItem: any): void; arrToRepeat(arr: any): any; deRepeatArr(seriesArr: any): unknown[]; byArrObjDeleteArrObj2(arrObj: any, arrObj2: any, objKey: any): any; deleteArrObjByKey(arrObj: any, objKey: any, value: any): any; findArrObjByKey(arrObj: any, objKey: any, value: any): any; byArrObjFindArrObj2(arrObj: any, arrObj2: any, objKey: any): any[]; }; export default _default; ================================================ FILE: ts-out-dir/src/utils/common-util.js ================================================ export default { getWeek() { return `星期${'日一二三四五六'.charAt(new Date().getDay())}`; }, mobilePhone(str) { const reg = /^0?1[0-9]{10}$/; return reg.test(str); }, toSplitNumFor(num, numToSpace) { return num.replace(/(.{4})/g, '$1 '); }, bankCardNo(str) { const reg = /^\d{15,20}$/; return reg.test(str); }, regEmail(str) { const reg = /^([a-zA-Z]|[0-9])(\w|-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/; return reg.test(str); }, idCardNumber(str) { const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; return reg.test(str); }, deleteArrItem(arr, arrItem) { arr.splice(arr.indexOf(arrItem), 1); }, arrToRepeat(arr) { return arr.filter((ele, index, thisArr) => { return thisArr.indexOf(ele) === index; }); }, deRepeatArr(seriesArr) { return [...new Set(seriesArr)]; }, byArrObjDeleteArrObj2(arrObj, arrObj2, objKey) { arrObj .map((value) => { return value[objKey]; }) .forEach((value2) => { arrObj2.splice(arrObj2.findIndex((item) => item[objKey] === value2), 1); }); return arrObj2; }, deleteArrObjByKey(arrObj, objKey, value) { arrObj.splice(arrObj.findIndex((item) => item[objKey] === value), 1); return arrObj; }, findArrObjByKey(arrObj, objKey, value) { return arrObj[arrObj.findIndex((item) => item[objKey] == value)]; }, byArrObjFindArrObj2(arrObj, arrObj2, objKey) { const arrObj3 = []; arrObj .map((value) => { return value[objKey]; }) .forEach((value2) => { const arrIndex = arrObj2.findIndex((item) => item[objKey] === value2); if (arrIndex !== -1) { arrObj3.push(arrObj2[arrIndex]); } }); return arrObj3; } }; ================================================ FILE: ts-out-dir/src/views/redirect/index.d.ts ================================================ declare const _default: import("vue").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly>, {}>; export default _default; ================================================ FILE: ts-out-dir/src/views/redirect/index.jsx ================================================ import { defineComponent } from 'vue'; export default defineComponent({ setup() { const route = useRoute(); const router = useRouter(); onBeforeMount(() => { const { params, query } = route; const { path } = params; router.replace({ path: `/${path}`, query }); }); return () =>
; } }); ================================================ FILE: tsconfig.base.json ================================================ { //设置files为空,则不会自动扫描默认目录,也就是只会扫描include配置的目录 "files": [], "compilerOptions": { "target": "esnext", "module": "esnext", //启用所有严格类型检查选项。 //启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization。 "strict": true, // 允许编译器编译JS,JSX文件 "allowJs": false, // 允许在JS文件中报错,通常与allowJS一起使用 "checkJs": false, // 允许使用jsx "jsx": "preserve", "declaration": true, //移除注解 "removeComments": true, //不可以忽略any "noImplicitAny": false, //关闭 this 类型注解提示 "noImplicitThis": true, //null/undefined不能作为其他类型的子类型: //let a: number = null; //这里会报错. "strictNullChecks": true, //生成枚举的映射代码 "preserveConstEnums": true, //根目录 //输出目录 "outDir": "./ts-out-dir", //是否输出src2.js.map文件 "sourceMap": false, //变量定义了但是未使用 "noUnusedLocals": false, //是否允许把json文件当做模块进行解析 "resolveJsonModule": true, //和noUnusedLocals一样,针对func "noUnusedParameters": false, // 模块解析策略,ts默认用node的解析策略,即相对的方式导入 "moduleResolution": "node", //允许export=导出,由import from 导入 "esModuleInterop": true, //忽略所有的声明文件( *.d.ts)的类型检查。 "skipLibCheck": true, "baseUrl": ".", //指定默认读取的目录 //"typeRoots": ["./node_modules/@types/", "./types"], "lib": ["ES2018", "DOM"] } } ================================================ FILE: tsconfig.json ================================================ { "extends": "./tsconfig.base.json", "compilerOptions": { "paths": { "@/*": ["src/*"], "~/*": ["typings/*"] } }, "include": ["src", "typings"], "exclude": ["node_modules", "**/dist"] } ================================================ FILE: typings/auto-imports.d.ts ================================================ // Generated by 'unplugin-auto-import' export {} declare global { const EffectScope: typeof import('vue')['EffectScope'] const axiosReq: typeof import('../src/utils/axios-req')['default'] const bus: typeof import('../src/utils/bus')['default'] const buttonCodes: typeof import('../src/directives/button-codes')['default'] const casHandleChange: typeof import('../src/hooks/use-element')['casHandleChange'] const cloneDeep: typeof import('../src/hooks/use-common')['cloneDeep'] const closeElLoading: typeof import('../src/hooks/use-element')['closeElLoading'] const codesPermission: typeof import('../src/directives/codes-permission')['default'] const commonUtil: typeof import('../src/utils/common-util')['default'] const computed: typeof import('vue')['computed'] const copyValueToClipboard: typeof import('../src/hooks/use-common')['copyValueToClipboard'] const createApp: typeof import('vue')['createApp'] const customRef: typeof import('vue')['customRef'] const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineComponent: typeof import('vue')['defineComponent'] const directives: typeof import('../src/directives/index')['default'] const effectScope: typeof import('vue')['effectScope'] const elConfirm: typeof import('../src/hooks/use-element')['elConfirm'] const elConfirmNoCancelBtn: typeof import('../src/hooks/use-element')['elConfirmNoCancelBtn'] const elLoading: typeof import('../src/hooks/use-element')['elLoading'] const elMessage: typeof import('../src/hooks/use-element')['elMessage'] const elNotify: typeof import('../src/hooks/use-element')['elNotify'] const filterAsyncRouter: typeof import('../src/hooks/use-permission')['filterAsyncRouter'] const filterAsyncRouterByCodes: typeof import('../src/hooks/use-permission')['filterAsyncRouterByCodes'] const filterAsyncRoutesByMenuList: typeof import('../src/hooks/use-permission')['filterAsyncRoutesByMenuList'] const filterAsyncRoutesByRoles: typeof import('../src/hooks/use-permission')['filterAsyncRoutesByRoles'] const freshRouter: typeof import('../src/hooks/use-permission')['freshRouter'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentScope: typeof import('vue')['getCurrentScope'] const getLangInstance: typeof import('../src/hooks/use-common')['getLangInstance'] const getQueryParam: typeof import('../src/hooks/use-self-router')['getQueryParam'] const h: typeof import('vue')['h'] const inject: typeof import('vue')['inject'] const isExternal: typeof import('../src/hooks/use-layout')['isExternal'] const isProxy: typeof import('vue')['isProxy'] const isReactive: typeof import('vue')['isReactive'] const isReadonly: typeof import('vue')['isReadonly'] const isRef: typeof import('vue')['isRef'] const lang: typeof import('../src/directives/lang')['default'] const langTitle: typeof import('../src/hooks/use-common')['langTitle'] const markRaw: typeof import('vue')['markRaw'] const nextTick: typeof import('vue')['nextTick'] const onActivated: typeof import('vue')['onActivated'] const onBeforeMount: typeof import('vue')['onBeforeMount'] const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] const onDeactivated: typeof import('vue')['onDeactivated'] const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onMounted: typeof import('vue')['onMounted'] const onRenderTracked: typeof import('vue')['onRenderTracked'] const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onScopeDispose: typeof import('vue')['onScopeDispose'] const onServerPrefetch: typeof import('vue')['onServerPrefetch'] const onUnmounted: typeof import('vue')['onUnmounted'] const onUpdated: typeof import('vue')['onUpdated'] const progressClose: typeof import('../src/hooks/use-permission')['progressClose'] const progressStart: typeof import('../src/hooks/use-permission')['progressStart'] const provide: typeof import('vue')['provide'] const reactive: typeof import('vue')['reactive'] const readonly: typeof import('vue')['readonly'] const ref: typeof import('vue')['ref'] const resetRouter: typeof import('../src/hooks/use-permission')['resetRouter'] const resetState: typeof import('../src/hooks/use-permission')['resetState'] const resizeHandler: typeof import('../src/hooks/use-layout')['resizeHandler'] const resolveComponent: typeof import('vue')['resolveComponent'] const resolveDirective: typeof import('vue')['resolveDirective'] const rolesPermission: typeof import('../src/directives/roles-permission')['default'] const routeInfo: typeof import('../src/hooks/use-self-router')['routeInfo'] const routerBack: typeof import('../src/hooks/use-self-router')['routerBack'] const routerPush: typeof import('../src/hooks/use-self-router')['routerPush'] const routerReplace: typeof import('../src/hooks/use-self-router')['routerReplace'] const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowRef: typeof import('vue')['shallowRef'] const sleepTimeout: typeof import('../src/hooks/use-common')['sleepTimeout'] const storeToRefs: typeof import('pinia/dist/pinia')['storeToRefs'] const toRaw: typeof import('vue')['toRaw'] const toRef: typeof import('vue')['toRef'] const toRefs: typeof import('vue')['toRefs'] const triggerRef: typeof import('vue')['triggerRef'] const unref: typeof import('vue')['unref'] const useAttrs: typeof import('vue')['useAttrs'] const useBasicStore: typeof import('../src/store/basic')['useBasicStore'] const useConfigStore: typeof import('../src/store/config')['useConfigStore'] const useCssModule: typeof import('vue')['useCssModule'] const useCssVars: typeof import('vue')['useCssVars'] const useElement: typeof import('../src/hooks/use-element')['useElement'] const useErrorLog: typeof import('../src/hooks/use-error-log')['useErrorLog'] const useLink: typeof import('vue-router')['useLink'] const useRoute: typeof import('vue-router')['useRoute'] const useRouter: typeof import('vue-router')['useRouter'] const useSlots: typeof import('vue')['useSlots'] const useTable: typeof import('../src/hooks/use-table')['useTable'] const useTagsViewStore: typeof import('../src/store/tags-view')['useTagsViewStore'] const watch: typeof import('vue')['watch'] const watchEffect: typeof import('vue')['watchEffect'] const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchSyncEffect: typeof import('vue')['watchSyncEffect'] } ================================================ FILE: typings/basic.d.ts ================================================ /* * 声明.d.ts文件规范 * 导出的类型以大写开头 * 对象:config * 数组:options * 枚举:emu * 函数:Fn * 属性:props * 实例:instance * */ /*router*/ import type { RouteRecordRaw } from 'vue-router' export interface rawConfig { hidden?: boolean alwaysShow?: boolean code?: number name?: string fullPath?: string path?: string meta?: { title: string icon?: string affix?: boolean activeMenu?: string breadcrumb?: boolean roles?: Array elSvgIcon?: string code?: number cachePage?: boolean leaveRmCachePage?: boolean closeTabRmCache?: boolean } children?: RouterOptions redirect?: string } export type RouteRawConfig = RouteRecordRaw & rawConfig export type RouterTypes = Array /*settings*/ export interface SettingsConfig { title: string sidebarLogo: boolean showLeftMenu: boolean ShowDropDown: boolean showHamburger: boolean isNeedLogin: boolean isNeedNprogress: boolean showTagsView: boolean tagsViewNum: number openProdMock: boolean errorLog: string | Array permissionMode: string delWindowHeight: string tmpToken: string showNavbarTitle: boolean showTopNavbar: boolean mainNeedAnimation: boolean viteBasePath: string defaultLanguage: string defaultSize: string defaultTheme: string plateFormId: number } export {} ================================================ FILE: typings/components.d.ts ================================================ // generated by unplugin-vue-components // We suggest you to commit this file into source control // Read more: https://github.com/vuejs/core/pull/3399 import '@vue/runtime-core' export {} declare module '@vue/runtime-core' { export interface GlobalComponents { ElSvgIcon: typeof import('./../src/components/ElSvgIcon.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SvgIcon: typeof import('./../src/icons/SvgIcon.vue')['default'] TestUnit: typeof import('./../src/components/TestUnit.vue')['default'] } } ================================================ FILE: typings/env.d.ts ================================================ declare global { interface ImportMetaEnv { readonly VITE_APP_BASE_URL: string readonly VITE_APP_IMAGE_URL: string readonly VITE_APP_ENV: string // 更多环境变量... } interface ImportMeta { readonly env: ImportMetaEnv } } export {} ================================================ FILE: typings/global.d.ts ================================================ import type { defineOptions as _defineOptions } from 'unplugin-vue-define-options/macros.d.ts' declare global { interface ObjKeys { [propName: string]: any } const GLOBAL_VAR: String const defineOptions: typeof _defineOptions const $ref: any } export {} ================================================ FILE: typings/shims-vue.d.ts ================================================ /*fix the import warning issue of vue file*/ declare module '*.vue' { import { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component } ================================================ FILE: vite.config.ts ================================================ import { resolve } from 'path' import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { viteMockServe } from 'vite-plugin-mock' import Components from 'unplugin-vue-components/vite' import UnoCSS from 'unocss/vite' import { presetAttributify, presetIcons, presetUno } from 'unocss' import mkcert from 'vite-plugin-mkcert' import AutoImport from 'unplugin-auto-import/vite' import setting from './src/settings' import vitePluginSetupExtend from './src/plugins/vite-plugin-setup-extend' const prodMock = setting.openProdMock // import { visualizer } from 'rollup-plugin-visualizer' const pathSrc = resolve(__dirname, 'src') // @ts-ignore export default defineConfig(({ command, mode }) => { //const env = loadEnv(mode, process.cwd(), '') //获取环境变量 return { base: setting.viteBasePath, define: { //define global var GLOBAL_STRING: JSON.stringify('i am global var from vite.config.js define'), GLOBAL_VAR: { test: 'i am global var from vite.config.js define' } }, clearScreen: false, //设为 false 可以避免 Vite 清屏而错过在终端中打印某些关键信息 server: { hmr: { overlay: false }, //设置 server.hmr.overlay 为 false 可以禁用开发服务器错误的屏蔽。方便错误查看 port: 5003, // 类型: number 指定服务器端口; open: false, // 类型: boolean | string在服务器启动时自动在浏览器中打开应用程序; host: true, https: false }, preview: { port: 5006, host: true, strictPort: true }, plugins: [ vue(), vueJsx(), UnoCSS({ presets: [presetUno(), presetAttributify(), presetIcons()] }), mkcert(), //compatible with old browsers // legacy({ // targets: ['chrome 52'], // additionalLegacyPolyfills: ['regenerator-runtime/runtime'] // }), createSvgIconsPlugin({ iconDirs: [resolve(process.cwd(), 'src/icons/common'), resolve(process.cwd(), 'src/icons/nav-bar')], symbolId: 'icon-[dir]-[name]' }), //https://github.com/anncwb/vite-plugin-mock/blob/HEAD/README.zh_CN.md viteMockServe({ enable:true, mockPath: 'mock', // prodEnabled: prodMock, // injectCode: ` // import { setupProdMockServer } from './mock-prod-server'; // setupProdMockServer(); // `, logger: true }), // VueSetupExtend(),using DefineOptions instant of it //https://github.com/antfu/unplugin-auto-import/blob/HEAD/src/types.ts Components({ dirs: ['src/components', 'src/icons'], extensions: ['vue'], deep: true, dts: './typings/components.d.ts' }), AutoImport({ imports: [ 'vue', 'vue-router', { 'pinia/dist/pinia': ['storeToRefs'] } ], //配置后会自动扫描目录下的文件 dirs: ['src/hooks/**', 'src/utils/**', 'src/store/**', 'src/directives/**'], eslintrc: { enabled: true, // Default `false` filepath: './eslintrc/.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json` globalsPropValue: true // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable') }, dts: './typings/auto-imports.d.ts' }), // auto config of index.html title //依赖分析插件 // visualizer({ // open: true, // gzipSize: true, // brotliSize: true // }) vitePluginSetupExtend({ inject: { title: setting.title } }) ], build: { chunkSizeWarningLimit: 10000, //消除触发警告的 chunk, 默认500k assetsDir: 'static/assets', rollupOptions: { output: { chunkFileNames: 'static/js/[name]-[hash].js', entryFileNames: 'static/js/[name]-[hash].js', assetFileNames: 'static/[ext]/[name]-[hash].[ext]' } } }, resolve: { alias: { '@/': `${pathSrc}/`, 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js' //remove i18n waring } }, optimizeDeps: { //include: [...optimizeDependencies,...optimizeElementPlus] //on-demand element-plus use this include: ['moment-mini'] } } }) ================================================ FILE: vitest.config.ts ================================================ import { defineConfig } from 'vitest/config' import Vue from '@vitejs/plugin-vue' import VueJsx from '@vitejs/plugin-vue-jsx' import DefineOptions from 'unplugin-vue-define-options/vite' export default defineConfig({ // @ts-ignore plugins: [Vue(), VueJsx(), DefineOptions()], optimizeDeps: { disabled: true }, test: { clearMocks: true, environment: 'jsdom', //setup 文件的路径。它们将运行在每个测试文件之前。 setupFiles: ['./vitest.setup.ts'], transformMode: { web: [/\.[jt]sx$/] } } }) ================================================ FILE: vitest.setup.ts ================================================ import { config } from '@vue/test-utils' import { vi } from 'vitest' import ResizeObserver from 'resize-observer-polyfill' vi.stubGlobal('ResizeObserver', ResizeObserver) config.global.stubs = {}