commit 873d54056dd559c1b17da0c471166cba90eef639
Author: TheSmileCat <2098833867@qq.com>
Date: Thu Feb 2 16:07:38 2023 +0800
创建仓库
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e69de29
diff --git a/.env.build b/.env.build
new file mode 100644
index 0000000..43a43c7
--- /dev/null
+++ b/.env.build
@@ -0,0 +1,6 @@
+VITE_APP_ENV = 'prod'
+#自动获取地址推荐
+VITE_APP_BASE_URL = 'https://github.jzfai.top/micro-service-api'
+
+#image or oss address
+VITE_APP_IMAGE_URL = 'https://github.jzfai.top/gofast-image'
diff --git a/.env.build-test b/.env.build-test
new file mode 100644
index 0000000..d454976
--- /dev/null
+++ b/.env.build-test
@@ -0,0 +1,8 @@
+VITE_APP_ENV = 'test'
+#自动获取地址推荐
+#VITE_APP_BASE_URL = '/micro-service-api'
+VITE_APP_BASE_URL = 'https://github.jzfai.top/micro-service-api'
+VITE_APP_BASE_WS_URL = ''
+
+#image or oss address
+VITE_APP_IMAGE_URL = 'https://github.jzfai.top/gofast-image'
diff --git a/.env.serve-dev b/.env.serve-dev
new file mode 100644
index 0000000..a38d289
--- /dev/null
+++ b/.env.serve-dev
@@ -0,0 +1,10 @@
+#The defined variable must start with VITE_APP_
+VITE_APP_ENV = 'dev'
+VITE_APP_BASE_URL = 'https://github.jzfai.top/micro-service-api'
+
+#image or oss address
+VITE_APP_IMAGE_URL = 'https://github.jzfai.top/gofast-image'
+
+#proxy, use this to test proxy
+#VITE_APP_BASE_URL = '/api'
+#VITE_APP_PROXY_URL = 'https://github.jzfai.top/micro-service-api'
diff --git a/.env.serve-test b/.env.serve-test
new file mode 100644
index 0000000..ed14544
--- /dev/null
+++ b/.env.serve-test
@@ -0,0 +1,10 @@
+#The defined variable must start with VITE_APP_
+VITE_APP_ENV = 'test'
+VITE_APP_BASE_URL = 'https://github.jzfai.top/micro-service-api'
+
+ #image or oss address
+VITE_APP_IMAGE_URL = 'https://github.jzfai.top/gofast-image'
+
+#proxy, use this to test proxy
+#VITE_APP_BASE_URL = '/api'
+#VITE_APP_PROXY_URL = 'https://github.jzfai.top/micro-service-api'
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..14f5b39
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,6 @@
+public
+node_modules
+.history
+.husky
+dist
+*.d.ts
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..c7e7ebd
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,4 @@
+{
+ "root": true,
+ "extends": ["./eslintrc/eslint-config.js", "./eslintrc/.eslintrc-auto-import.json"]
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b5e33a8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,51 @@
+# 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
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..3b92648
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,5 @@
+#!/bin/sh
+#. "$(dirname "$0")/_/husky.sh"
+#在项目中我们会使用commit-msg这个git hook来校验我们commit时添加的备注信息是否符合规范。在以前的我们通常是这样配置:
+#--no-install 参数表示强制npx使用项目中node_modules目录中的commitlint包(如果需要开启,注意:需要安装npx)
+#npx --no-install commitlint --edit $1
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..db96295
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,8 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+#推送之前运行eslint检查
+npm run lint
+#推送之前运行单元测试检查
+#npm run test:unit
+
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..daf66a1
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,7 @@
+shamefully-hoist=true
+strict-peer-dependencies=false
+
+###aliyun address
+registry = https://registry.npmmirror.com
+
+
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..454eec7
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "useTabs": false,
+ "tabWidth": 2,
+ "printWidth": 120,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "bracketSpacing": true,
+ "semi": false,
+ "htmlWhitespaceSensitivity": "ignore"
+}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..0c87b22
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["johnsoncodehk.volar", "esbenp.prettier-vscode","dbaeumer.vscode-eslint"]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4029450
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "npm.packageManager": "yarn"
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ffef565
--- /dev/null
+++ b/README.md
@@ -0,0 +1,85 @@
+# vue3-admin-plus
+
+The plus version of vue3-admin-ts , provide enterprise-class using demo
+
+suggestion the Node.js >= 16.0.0。
+
+
+
+## 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-plus)
+
+[国内体验地址](https://github.jzfai.top/vue3-admin-plus)
+
+
+
+## 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-plus.git
+
+# enter the project directory
+cd vue3-admin-plus
+
+# 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)
+
+
+
diff --git a/eslintrc/.eslintrc-auto-import.json b/eslintrc/.eslintrc-auto-import.json
new file mode 100644
index 0000000..429c7b0
--- /dev/null
+++ b/eslintrc/.eslintrc-auto-import.json
@@ -0,0 +1,116 @@
+{
+ "globals": {
+ "EffectScope": true,
+ "axiosReq": true,
+ "bus": true,
+ "buttonCodes": true,
+ "casHandleChange": true,
+ "clickoutside": true,
+ "cloneDeep": true,
+ "closeElLoading": true,
+ "codesPermission": true,
+ "commonUtil": true,
+ "computed": true,
+ "copy": true,
+ "copyValueToClipboard": true,
+ "createApp": true,
+ "customRef": true,
+ "debounce": 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,
+ "loginOutReq": true,
+ "loginReq": true,
+ "longpress": true,
+ "markRaw": true,
+ "mockAxiosReq": 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,
+ "searchUser": true,
+ "shallowReactive": true,
+ "shallowReadonly": true,
+ "shallowRef": true,
+ "sleepTimeout": true,
+ "storeToRefs": true,
+ "toRaw": true,
+ "toRef": true,
+ "toRefs": true,
+ "transactionList": 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,
+ "userInfoReq": true,
+ "watch": true,
+ "watchEffect": true,
+ "watchPostEffect": true,
+ "watchSyncEffect": true,
+ "watermark": true,
+ "waves": true
+ }
+}
\ No newline at end of file
diff --git a/eslintrc/eslint-config.js b/eslintrc/eslint-config.js
new file mode 100644
index 0000000..34b37f7
--- /dev/null
+++ b/eslintrc/eslint-config.js
@@ -0,0 +1,200 @@
+// 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
+ },
+ globals: {
+ defineOptions: true,
+ $ref: 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': 'error',
+ // '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'
+ }
+})
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..b8226ee
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ <%= title %>
+
+
+
+
+
+
diff --git a/mock-prod-server.ts b/mock-prod-server.ts
new file mode 100644
index 0000000..1f931f7
--- /dev/null
+++ b/mock-prod-server.ts
@@ -0,0 +1,12 @@
+import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
+//https://cn.vitejs.dev/guide/features.html#glob-import
+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])
+}
diff --git a/mock/example.ts b/mock/example.ts
new file mode 100644
index 0000000..4f04247
--- /dev/null
+++ b/mock/example.ts
@@ -0,0 +1,12 @@
+export default [
+ {
+ url: '/getMapInfo',
+ method: 'get',
+ response: () => {
+ return {
+ code: 200,
+ title: 'mock请求测试'
+ }
+ }
+ }
+]
diff --git a/mock/excel.ts b/mock/excel.ts
new file mode 100644
index 0000000..b4631a7
--- /dev/null
+++ b/mock/excel.ts
@@ -0,0 +1,56 @@
+import Mock from 'mockjs'
+
+const NameList: any = []
+const count = 100
+
+for (let i = 0; i < count; i++) {
+ NameList.push(
+ Mock.mock({
+ name: '@first'
+ })
+ )
+}
+NameList.push({ name: 'mock-Pan' })
+
+export default [
+ // username search
+ {
+ url: '/vue3-admin-plus/search/user',
+ method: 'get',
+ response: (config) => {
+ const { name } = config.query
+ const mockNameList = NameList.filter((item) => {
+ // @ts-ignore
+ const lowerCaseName = item.name.toLowerCase()
+ return !(name && !lowerCaseName.includes(name.toLowerCase()))
+ })
+ return {
+ code: 20000,
+ data: { items: mockNameList }
+ }
+ }
+ },
+
+ // transaction list
+ {
+ url: '/vue3-admin-plus/transaction/list',
+ method: 'get',
+ response: () => {
+ return {
+ code: 20000,
+ data: {
+ total: 20,
+ 'items|20': [
+ {
+ order_no: '@guid()',
+ timestamp: +Mock.Random.date('T'),
+ username: '@name()',
+ price: '@float(1000, 15000, 0, 2)',
+ 'status|1': ['success', 'pending']
+ }
+ ]
+ }
+ }
+ }
+ }
+]
diff --git a/mock/table.ts b/mock/table.ts
new file mode 100644
index 0000000..d5723e5
--- /dev/null
+++ b/mock/table.ts
@@ -0,0 +1,31 @@
+import Mock from 'mockjs'
+
+const data = Mock.mock({
+ 'items|30': [
+ {
+ id: '@id',
+ title: '@sentence(10, 20)',
+ 'status|1': ['published', 'draft', 'deleted'],
+ author: 'name',
+ display_time: '@datetime',
+ pageviews: '@integer(300, 5000)'
+ }
+ ]
+})
+
+export default [
+ {
+ url: '/vue3-admin-template/table/list',
+ method: 'get',
+ response: () => {
+ const items = data.items
+ return {
+ code: 20000,
+ data: {
+ total: items.length,
+ items
+ }
+ }
+ }
+ }
+]
diff --git a/optimize-include.ts b/optimize-include.ts
new file mode 100644
index 0000000..a29553c
--- /dev/null
+++ b/optimize-include.ts
@@ -0,0 +1,133 @@
+// 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 []
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..b5fa43c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,129 @@
+{
+ "name": "vue3-admin-plus",
+ "version": "2.0.2",
+ "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",
+ "screenfull": "^6.0.2",
+ "axios": "^1.1.3",
+ "echarts": "^5.4.0",
+ "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-i18n": "9.1.10",
+ "vue-router": "^4.1.5",
+ "xlsx": "^0.18.5",
+ "d3": "7.2.1",
+ "splitpanes": "^3.1.1",
+ "vxe-table": "^4.3.5",
+ "xe-utils": "^3.5.6",
+ "tinymce": "^6.1.0",
+ "driver.js": "^0.9.8",
+ "sortablejs": "1.15.0"
+ },
+ "devDependencies": {
+ "@babel/eslint-parser": "7.16.3",
+ "@tinymce/tinymce-vue": "^5.0.0",
+ "@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",
+ "vite": "^4.0.2",
+ "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": [
+ "vite-plugin-mock",
+ "unplugin-auto-import",
+ "unplugin-vue-components",
+ "unocss",
+ "unplugin",
+ "vite-plugin-mock",
+ "@vitejs/plugin-legacy",
+ "@vitejs/plugin-vue",
+ "@vitejs/*",
+ "@babel/*",
+ "vite",
+ "vue",
+ "@unocss/vite",
+ "rollup",
+ "vue-jest"
+ ]
+ }
+ },
+ "browserslist": [
+ "> 1%",
+ "not ie 11",
+ "not op_mini all"
+ ],
+ "engines": {
+ "node": ">= 16 <18",
+ "pnpm": ">= 6 <8"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..b3e50e3
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..4c2f0fa
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
diff --git a/src/api/remote-search.ts b/src/api/remote-search.ts
new file mode 100644
index 0000000..457e9ae
--- /dev/null
+++ b/src/api/remote-search.ts
@@ -0,0 +1,17 @@
+import request from '@/utils/mock-axios-req'
+
+export function searchUser(name) {
+ return request({
+ url: '/vue3-admin-plus/search/user',
+ method: 'get',
+ params: { name }
+ })
+}
+
+export function transactionList(query) {
+ return request({
+ url: '/vue3-admin-plus/transaction/list',
+ method: 'get',
+ params: query
+ })
+}
diff --git a/src/api/user.ts b/src/api/user.ts
new file mode 100644
index 0000000..c003a5c
--- /dev/null
+++ b/src/api/user.ts
@@ -0,0 +1,31 @@
+//获取用户信息
+import axiosReq from '@/utils/axios-req'
+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: '/basis-func/user/loginValid',
+ params: subForm,
+ method: 'post'
+ })
+}
+
+//退出登录
+export const loginOutReq = () => {
+ return axiosReq({
+ url: '/basis-func/user/loginValid',
+ method: 'post'
+ })
+}
diff --git a/src/assets/401_images/401.gif b/src/assets/401_images/401.gif
new file mode 100644
index 0000000..cd6e0d9
Binary files /dev/null and b/src/assets/401_images/401.gif differ
diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png
new file mode 100644
index 0000000..3d8e230
Binary files /dev/null and b/src/assets/404_images/404.png differ
diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png
new file mode 100644
index 0000000..c6281d0
Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ
diff --git a/src/assets/layout/animation-image.gif b/src/assets/layout/animation-image.gif
new file mode 100644
index 0000000..fdbd32c
Binary files /dev/null and b/src/assets/layout/animation-image.gif differ
diff --git a/src/assets/layout/login-bg.svg b/src/assets/layout/login-bg.svg
new file mode 100644
index 0000000..ab3dab9
--- /dev/null
+++ b/src/assets/layout/login-bg.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/layout/login-front.svg b/src/assets/layout/login-front.svg
new file mode 100644
index 0000000..b96a536
--- /dev/null
+++ b/src/assets/layout/login-front.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/layout/login-top.svg b/src/assets/layout/login-top.svg
new file mode 100644
index 0000000..ca5cac8
--- /dev/null
+++ b/src/assets/layout/login-top.svg
@@ -0,0 +1,21 @@
+
diff --git a/src/assets/layout/login.svg b/src/assets/layout/login.svg
new file mode 100644
index 0000000..9ac16cb
--- /dev/null
+++ b/src/assets/layout/login.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/layout/logo.png b/src/assets/layout/logo.png
new file mode 100644
index 0000000..9838306
Binary files /dev/null and b/src/assets/layout/logo.png differ
diff --git a/src/components/ElSvgIcon.vue b/src/components/ElSvgIcon.vue
new file mode 100644
index 0000000..c7c60cd
--- /dev/null
+++ b/src/components/ElSvgIcon.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/TestUnit.vue b/src/components/TestUnit.vue
new file mode 100644
index 0000000..499707a
--- /dev/null
+++ b/src/components/TestUnit.vue
@@ -0,0 +1,15 @@
+
+ TestUnit.vue
+
+
+
+
+
diff --git a/src/components/__tests__/el-svgIcon.test.jsx b/src/components/__tests__/el-svgIcon.test.jsx
new file mode 100644
index 0000000..238a998
--- /dev/null
+++ b/src/components/__tests__/el-svgIcon.test.jsx
@@ -0,0 +1,236 @@
+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')
+// })
+// })
diff --git a/src/directives/button-codes.ts b/src/directives/button-codes.ts
new file mode 100644
index 0000000..8a7db9b
--- /dev/null
+++ b/src/directives/button-codes.ts
@@ -0,0 +1,21 @@
+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)
+ }
+}
diff --git a/src/directives/codes-permission.ts b/src/directives/codes-permission.ts
new file mode 100644
index 0000000..1d0bf24
--- /dev/null
+++ b/src/directives/codes-permission.ts
@@ -0,0 +1,20 @@
+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)
+ }
+}
diff --git a/src/directives/example/clickoutside.js b/src/directives/example/clickoutside.js
new file mode 100644
index 0000000..f05ef9b
--- /dev/null
+++ b/src/directives/example/clickoutside.js
@@ -0,0 +1,21 @@
+
+export default {
+ mounted(el, binding) {
+ if (typeof binding.value !== 'function') {
+ throw 'callback must be a function';
+ }
+ el.__handleClick__ = function (e) {
+ if (el.contains(e.target)) {
+ binding.value(false);
+ }
+ else {
+ binding.value(true);
+ }
+ };
+ document.addEventListener('click', el.__handleClick__);
+ },
+ beforeUnmount(el) {
+ document.removeEventListener('click', el.__handleClick__);
+ }
+
+}
diff --git a/src/directives/example/copy.js b/src/directives/example/copy.js
new file mode 100644
index 0000000..9316355
--- /dev/null
+++ b/src/directives/example/copy.js
@@ -0,0 +1,31 @@
+/**
+ * v-copy
+ * 复制某个值至剪贴板
+ * 接收参数:string类型/Ref类型/Reactive类型
+ */
+import { ElMessage } from 'element-plus';
+function handleClick(ev) {
+ const input = document.createElement('input');
+ input.value = this.copyData.toLocaleString();
+ document.body.appendChild(input);
+ input.select();
+ document.execCommand('Copy');
+ document.body.removeChild(input);
+ ElMessage({
+ type: 'success',
+ message: '复制成功'
+ });
+}
+export default {
+ mounted(el, binding) {
+ el.copyData = binding.value;
+ el.addEventListener('click', handleClick);
+ },
+ updated(el, binding) {
+ el.copyData = binding.value;
+ },
+ beforeUnmount(el) {
+ el.removeEventListener('click', el.__handleClick__);
+ }
+};
+
diff --git a/src/directives/example/debounce.js b/src/directives/example/debounce.js
new file mode 100644
index 0000000..001021e
--- /dev/null
+++ b/src/directives/example/debounce.js
@@ -0,0 +1,26 @@
+/**
+ * v-debounce
+ * 按钮防抖指令,可自行扩展至input
+ * 接收参数:function类型
+ */
+export default {
+ mounted(el, binding) {
+ if (typeof binding.value !== 'function') {
+ console.error('callback must be a function');
+ return;
+ }
+ let timer = null;
+ el.__handleClick__ = function (e) {
+ if (timer) {
+ clearInterval(timer);
+ }
+ timer = setTimeout(() => {
+ binding.value();
+ }, 200);
+ };
+ el.addEventListener('click', el.__handleClick__);
+ },
+ beforeUnmount(el) {
+ el.removeEventListener('click', el.__handleClick__);
+ }
+};
diff --git a/src/directives/example/longpress.js b/src/directives/example/longpress.js
new file mode 100644
index 0000000..c76f977
--- /dev/null
+++ b/src/directives/example/longpress.js
@@ -0,0 +1,45 @@
+/**
+ * v-longpress
+ * 长按指令,长按时触发事件
+ */
+export default {
+ mounted(el, binding) {
+ if (typeof binding.value !== 'function') {
+ throw 'callback must be a function';
+ }
+ // 定义变量
+ let pressTimer = null;
+ // 创建计时器( 2秒后执行函数 )
+ const start = (e) => {
+ if (e.button) {
+ if (e.type === 'click' && e.button !== 0) {
+ return;
+ }
+ }
+ if (pressTimer === null) {
+ pressTimer = setTimeout(() => {
+ handler(e);
+ }, 1000);
+ }
+ };
+ // 取消计时器
+ const cancel = (e) => {
+ if (pressTimer !== null) {
+ clearTimeout(pressTimer);
+ pressTimer = null;
+ }
+ };
+ // 运行函数
+ const handler = (e) => {
+ binding.value(e);
+ };
+ // 添加事件监听器
+ el.addEventListener('mousedown', start);
+ el.addEventListener('touchstart', start);
+ // 取消计时器
+ el.addEventListener('click', cancel);
+ el.addEventListener('mouseout', cancel);
+ el.addEventListener('touchend', cancel);
+ el.addEventListener('touchcancel', cancel);
+ },
+};
diff --git a/src/directives/example/watermark.js b/src/directives/example/watermark.js
new file mode 100644
index 0000000..49cbf03
--- /dev/null
+++ b/src/directives/example/watermark.js
@@ -0,0 +1,28 @@
+/**
+ * v-watermark可接收参数,均为非必填
+ * { text: 'vue-admin-box', font: '16px Microsoft JhengHei', textColor: '#000' }
+ */
+function addWaterMark(str, parentNode, font, textColor) {
+ // 水印文字,父元素,字体,文字颜色
+ const can = document.createElement('canvas');
+ parentNode.appendChild(can);
+ can.width = 200;
+ can.height = 150;
+ can.style.display = 'none';
+ const cans = can.getContext('2d');
+ cans.rotate((-20 * Math.PI) / 180);
+ cans.font = font || '16px Microsoft JhengHei';
+ cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)';
+ cans.textAlign = 'left';
+ cans.textBaseline = 'middle';
+ cans.fillText(str || 'vue3-admin-plus', can.width / 10, can.height / 2);
+ parentNode.style.backgroundImage = `url(${ can.toDataURL('image/png') })`;
+}
+export default {
+ mounted(el, binding) {
+ binding.value ? binding.value : binding.value = {};
+ addWaterMark(binding.value.text, el, binding.value.font, binding.value.textColor);
+ }
+}
+
+
diff --git a/src/directives/example/waves.css b/src/directives/example/waves.css
new file mode 100644
index 0000000..af7a7ef
--- /dev/null
+++ b/src/directives/example/waves.css
@@ -0,0 +1,26 @@
+.waves-ripple {
+ position: absolute;
+ border-radius: 100%;
+ background-color: rgba(0, 0, 0, 0.15);
+ background-clip: padding-box;
+ pointer-events: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-transform: scale(0);
+ -ms-transform: scale(0);
+ transform: scale(0);
+ opacity: 1;
+}
+
+.waves-ripple.z-active {
+ opacity: 0;
+ -webkit-transform: scale(2);
+ -ms-transform: scale(2);
+ transform: scale(2);
+ -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+ transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
+ transition: opacity 1.2s ease-out, transform 0.6s ease-out;
+ transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
+}
\ No newline at end of file
diff --git a/src/directives/example/waves.js b/src/directives/example/waves.js
new file mode 100644
index 0000000..7fa67e6
--- /dev/null
+++ b/src/directives/example/waves.js
@@ -0,0 +1,72 @@
+import './waves.css'
+
+const context = '@@wavesContext'
+
+function handleClick(el, binding) {
+ function handle(e) {
+ const customOpts = Object.assign({}, binding.value)
+ const opts = Object.assign({
+ ele: el, // 波纹作用元素
+ type: 'hit', // hit 点击位置扩散 center中心点扩展
+ color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
+ },
+ customOpts
+ )
+ const target = opts.ele
+ if (target) {
+ target.style.position = 'relative'
+ target.style.overflow = 'hidden'
+ const rect = target.getBoundingClientRect()
+ let ripple = target.querySelector('.waves-ripple')
+ if (!ripple) {
+ ripple = document.createElement('span')
+ ripple.className = 'waves-ripple'
+ ripple.style.height = ripple.style.width = `${Math.max(rect.width, rect.height) }px`
+ target.appendChild(ripple)
+ } else {
+ ripple.className = 'waves-ripple'
+ }
+ switch (opts.type) {
+ case 'center':
+ ripple.style.top = `${rect.height / 2 - ripple.offsetHeight / 2 }px`
+ ripple.style.left = `${rect.width / 2 - ripple.offsetWidth / 2 }px`
+ break
+ default:
+ ripple.style.top =
+ `${e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
+ document.body.scrollTop }px`
+ ripple.style.left =
+ `${e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
+ document.body.scrollLeft }px`
+ }
+ ripple.style.backgroundColor = opts.color
+ ripple.className = 'waves-ripple z-active'
+ return false
+ }
+ }
+
+ if (!el[context]) {
+ el[context] = {
+ removeHandle: handle
+ }
+ } else {
+ el[context].removeHandle = handle
+ }
+
+ return handle
+}
+
+export default {
+ mounted(el, binding) {
+ el.addEventListener('click', handleClick(el, binding), false)
+ },
+ updated(el, binding) {
+ el.removeEventListener('click', el[context].removeHandle, false)
+ el.addEventListener('click', handleClick(el, binding), false)
+ },
+ beforeUnmount(el) {
+ el.removeEventListener('click', el[context].removeHandle, false)
+ el[context] = null
+ delete el[context]
+ }
+}
diff --git a/src/directives/index.ts b/src/directives/index.ts
new file mode 100644
index 0000000..45ab0fb
--- /dev/null
+++ b/src/directives/index.ts
@@ -0,0 +1,26 @@
+import buttonCodes from './button-codes'
+import codesPermission from './codes-permission'
+import rolesPermission from './roles-permission'
+import lang from './lang'
+
+import copy from './example/copy'
+import longpress from './example/longpress'
+import debounce from './example/debounce'
+import watermark from './example/watermark'
+import waves from './example/waves.js'
+import clickoutside from './example/clickoutside'
+
+export default function (app) {
+ app.directive('ButtonCodes', buttonCodes)
+ app.directive('CodesPermission', codesPermission)
+ app.directive('RolesPermission', rolesPermission)
+ app.directive('lang', lang)
+
+ //example
+ app.directive('copy', copy)
+ app.directive('longpress', longpress)
+ app.directive('debounce', debounce)
+ app.directive('watermark', watermark)
+ app.directive('waves', waves)
+ app.directive('clickoutside', clickoutside)
+}
diff --git a/src/directives/lang.ts b/src/directives/lang.ts
new file mode 100644
index 0000000..d66c76c
--- /dev/null
+++ b/src/directives/lang.ts
@@ -0,0 +1,44 @@
+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)
+ }
+}
diff --git a/src/directives/roles-permission.ts b/src/directives/roles-permission.ts
new file mode 100644
index 0000000..1b80598
--- /dev/null
+++ b/src/directives/roles-permission.ts
@@ -0,0 +1,20 @@
+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)
+ }
+}
diff --git a/src/hooks/use-common.ts b/src/hooks/use-common.ts
new file mode 100644
index 0000000..5ed49ac
--- /dev/null
+++ b/src/hooks/use-common.ts
@@ -0,0 +1,47 @@
+//复制文本
+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
+}
diff --git a/src/hooks/use-element.ts b/src/hooks/use-element.ts
new file mode 100644
index 0000000..3079482
--- /dev/null
+++ b/src/hooks/use-element.ts
@@ -0,0 +1,214 @@
+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
+}
diff --git a/src/hooks/use-error-log.ts b/src/hooks/use-error-log.ts
new file mode 100644
index 0000000..a59d854
--- /dev/null
+++ b/src/hooks/use-error-log.ts
@@ -0,0 +1,32 @@
+/*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: string) => {
+ 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 = () => {
+ //判断该环境是否需要收集错误日志,由settings配置决定
+ if (settings.errorLog?.includes(import.meta.env.VITE_APP_ENV)) {
+ jsErrorCollection({ runtimeError: true, rejectError: true, consoleError: true }, (errLog) => {
+ //判断是否是reqUrl错误,避免死循环
+ if (!errLog.includes(reqUrl)) errorLogReq(errLog)
+ })
+ }
+}
diff --git a/src/hooks/use-layout.ts b/src/hooks/use-layout.ts
new file mode 100644
index 0000000..5b4eab2
--- /dev/null
+++ b/src/hooks/use-layout.ts
@@ -0,0 +1,44 @@
+/**
+ * 判断是否是外链
+ * @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)
+ })
+}
diff --git a/src/hooks/use-permission.ts b/src/hooks/use-permission.ts
new file mode 100644
index 0000000..2c78780
--- /dev/null
+++ b/src/hooks/use-permission.ts
@@ -0,0 +1,184 @@
+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()
+}
diff --git a/src/hooks/use-self-router.ts b/src/hooks/use-self-router.ts
new file mode 100644
index 0000000..b307e6d
--- /dev/null
+++ b/src/hooks/use-self-router.ts
@@ -0,0 +1,43 @@
+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)
+}
diff --git a/src/hooks/use-table.ts b/src/hooks/use-table.ts
new file mode 100644
index 0000000..3d817b3
--- /dev/null
+++ b/src/hooks/use-table.ts
@@ -0,0 +1,122 @@
+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
+ }
+}
diff --git a/src/icons/SvgIcon.vue b/src/icons/SvgIcon.vue
new file mode 100644
index 0000000..208ac15
--- /dev/null
+++ b/src/icons/SvgIcon.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
diff --git a/src/icons/common/404.svg b/src/icons/common/404.svg
new file mode 100644
index 0000000..e69de29
diff --git a/src/icons/common/bug.svg b/src/icons/common/bug.svg
new file mode 100644
index 0000000..05a150d
--- /dev/null
+++ b/src/icons/common/bug.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/chart.svg b/src/icons/common/chart.svg
new file mode 100644
index 0000000..27728fb
--- /dev/null
+++ b/src/icons/common/chart.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/clipboard.svg b/src/icons/common/clipboard.svg
new file mode 100644
index 0000000..90923ff
--- /dev/null
+++ b/src/icons/common/clipboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/component.svg b/src/icons/common/component.svg
new file mode 100644
index 0000000..207ada3
--- /dev/null
+++ b/src/icons/common/component.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/dashboard.svg b/src/icons/common/dashboard.svg
new file mode 100644
index 0000000..5317d37
--- /dev/null
+++ b/src/icons/common/dashboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/demo.svg b/src/icons/common/demo.svg
new file mode 100644
index 0000000..05a150d
--- /dev/null
+++ b/src/icons/common/demo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/documentation.svg b/src/icons/common/documentation.svg
new file mode 100644
index 0000000..7043122
--- /dev/null
+++ b/src/icons/common/documentation.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/drag.svg b/src/icons/common/drag.svg
new file mode 100644
index 0000000..4185d3c
--- /dev/null
+++ b/src/icons/common/drag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/edit.svg b/src/icons/common/edit.svg
new file mode 100644
index 0000000..d26101f
--- /dev/null
+++ b/src/icons/common/edit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/education.svg b/src/icons/common/education.svg
new file mode 100644
index 0000000..7bfb01d
--- /dev/null
+++ b/src/icons/common/education.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/email.svg b/src/icons/common/email.svg
new file mode 100644
index 0000000..74d25e2
--- /dev/null
+++ b/src/icons/common/email.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/example.svg b/src/icons/common/example.svg
new file mode 100644
index 0000000..46f42b5
--- /dev/null
+++ b/src/icons/common/example.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/excel.svg b/src/icons/common/excel.svg
new file mode 100644
index 0000000..74d97b8
--- /dev/null
+++ b/src/icons/common/excel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/exit-fullscreen.svg b/src/icons/common/exit-fullscreen.svg
new file mode 100644
index 0000000..485c128
--- /dev/null
+++ b/src/icons/common/exit-fullscreen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/eye-open.svg b/src/icons/common/eye-open.svg
new file mode 100644
index 0000000..88dcc98
--- /dev/null
+++ b/src/icons/common/eye-open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/eye.svg b/src/icons/common/eye.svg
new file mode 100644
index 0000000..16ed2d8
--- /dev/null
+++ b/src/icons/common/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/form.svg b/src/icons/common/form.svg
new file mode 100644
index 0000000..dcbaa18
--- /dev/null
+++ b/src/icons/common/form.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/fullscreen.svg b/src/icons/common/fullscreen.svg
new file mode 100644
index 0000000..0e86b6f
--- /dev/null
+++ b/src/icons/common/fullscreen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/guide.svg b/src/icons/common/guide.svg
new file mode 100644
index 0000000..b271001
--- /dev/null
+++ b/src/icons/common/guide.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/hamburger.svg b/src/icons/common/hamburger.svg
new file mode 100644
index 0000000..9766c95
--- /dev/null
+++ b/src/icons/common/hamburger.svg
@@ -0,0 +1,2 @@
+
\ No newline at end of file
diff --git a/src/icons/common/icon.svg b/src/icons/common/icon.svg
new file mode 100644
index 0000000..82be8ee
--- /dev/null
+++ b/src/icons/common/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/international.svg b/src/icons/common/international.svg
new file mode 100644
index 0000000..e9b56ee
--- /dev/null
+++ b/src/icons/common/international.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/language.svg b/src/icons/common/language.svg
new file mode 100644
index 0000000..0082b57
--- /dev/null
+++ b/src/icons/common/language.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/link.svg b/src/icons/common/link.svg
new file mode 100644
index 0000000..48197ba
--- /dev/null
+++ b/src/icons/common/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/list.svg b/src/icons/common/list.svg
new file mode 100644
index 0000000..20259ed
--- /dev/null
+++ b/src/icons/common/list.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/lock.svg b/src/icons/common/lock.svg
new file mode 100644
index 0000000..74fee54
--- /dev/null
+++ b/src/icons/common/lock.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/message.svg b/src/icons/common/message.svg
new file mode 100644
index 0000000..14ca817
--- /dev/null
+++ b/src/icons/common/message.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/money.svg b/src/icons/common/money.svg
new file mode 100644
index 0000000..c1580de
--- /dev/null
+++ b/src/icons/common/money.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/nested.svg b/src/icons/common/nested.svg
new file mode 100644
index 0000000..06713a8
--- /dev/null
+++ b/src/icons/common/nested.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/password.svg b/src/icons/common/password.svg
new file mode 100644
index 0000000..e291d85
--- /dev/null
+++ b/src/icons/common/password.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/pdf.svg b/src/icons/common/pdf.svg
new file mode 100644
index 0000000..957aa0c
--- /dev/null
+++ b/src/icons/common/pdf.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/people.svg b/src/icons/common/people.svg
new file mode 100644
index 0000000..2bd54ae
--- /dev/null
+++ b/src/icons/common/people.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/peoples.svg b/src/icons/common/peoples.svg
new file mode 100644
index 0000000..aab852e
--- /dev/null
+++ b/src/icons/common/peoples.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/qq.svg b/src/icons/common/qq.svg
new file mode 100644
index 0000000..ee13d4e
--- /dev/null
+++ b/src/icons/common/qq.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/search.svg b/src/icons/common/search.svg
new file mode 100644
index 0000000..84233dd
--- /dev/null
+++ b/src/icons/common/search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/shopping.svg b/src/icons/common/shopping.svg
new file mode 100644
index 0000000..87513e7
--- /dev/null
+++ b/src/icons/common/shopping.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/sidebar-logo.svg b/src/icons/common/sidebar-logo.svg
new file mode 100644
index 0000000..1825fb3
--- /dev/null
+++ b/src/icons/common/sidebar-logo.svg
@@ -0,0 +1,2 @@
+
diff --git a/src/icons/common/size.svg b/src/icons/common/size.svg
new file mode 100644
index 0000000..ddb25b8
--- /dev/null
+++ b/src/icons/common/size.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/skill.svg b/src/icons/common/skill.svg
new file mode 100644
index 0000000..a3b7312
--- /dev/null
+++ b/src/icons/common/skill.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/star.svg b/src/icons/common/star.svg
new file mode 100644
index 0000000..6cf86e6
--- /dev/null
+++ b/src/icons/common/star.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/tab.svg b/src/icons/common/tab.svg
new file mode 100644
index 0000000..b4b48e4
--- /dev/null
+++ b/src/icons/common/tab.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/table.svg b/src/icons/common/table.svg
new file mode 100644
index 0000000..0e3dc9d
--- /dev/null
+++ b/src/icons/common/table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/theme.svg b/src/icons/common/theme.svg
new file mode 100644
index 0000000..5982a2f
--- /dev/null
+++ b/src/icons/common/theme.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/tree-table.svg b/src/icons/common/tree-table.svg
new file mode 100644
index 0000000..8aafdb8
--- /dev/null
+++ b/src/icons/common/tree-table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/tree.svg b/src/icons/common/tree.svg
new file mode 100644
index 0000000..dd4b7dd
--- /dev/null
+++ b/src/icons/common/tree.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/user.svg b/src/icons/common/user.svg
new file mode 100644
index 0000000..0ba0716
--- /dev/null
+++ b/src/icons/common/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/wechat.svg b/src/icons/common/wechat.svg
new file mode 100644
index 0000000..c586e55
--- /dev/null
+++ b/src/icons/common/wechat.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/common/zip.svg b/src/icons/common/zip.svg
new file mode 100644
index 0000000..e69de29
diff --git a/src/icons/nav-bar/dashboard.svg b/src/icons/nav-bar/dashboard.svg
new file mode 100644
index 0000000..5317d37
--- /dev/null
+++ b/src/icons/nav-bar/dashboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/example.svg b/src/icons/nav-bar/example.svg
new file mode 100644
index 0000000..46f42b5
--- /dev/null
+++ b/src/icons/nav-bar/example.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/eye-open.svg b/src/icons/nav-bar/eye-open.svg
new file mode 100644
index 0000000..88dcc98
--- /dev/null
+++ b/src/icons/nav-bar/eye-open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/eye.svg b/src/icons/nav-bar/eye.svg
new file mode 100644
index 0000000..16ed2d8
--- /dev/null
+++ b/src/icons/nav-bar/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/form.svg b/src/icons/nav-bar/form.svg
new file mode 100644
index 0000000..dcbaa18
--- /dev/null
+++ b/src/icons/nav-bar/form.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/language.svg b/src/icons/nav-bar/language.svg
new file mode 100644
index 0000000..4b8ed72
--- /dev/null
+++ b/src/icons/nav-bar/language.svg
@@ -0,0 +1 @@
+
diff --git a/src/icons/nav-bar/link.svg b/src/icons/nav-bar/link.svg
new file mode 100644
index 0000000..48197ba
--- /dev/null
+++ b/src/icons/nav-bar/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/nested.svg b/src/icons/nav-bar/nested.svg
new file mode 100644
index 0000000..06713a8
--- /dev/null
+++ b/src/icons/nav-bar/nested.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/password.svg b/src/icons/nav-bar/password.svg
new file mode 100644
index 0000000..e291d85
--- /dev/null
+++ b/src/icons/nav-bar/password.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/table.svg b/src/icons/nav-bar/table.svg
new file mode 100644
index 0000000..0e3dc9d
--- /dev/null
+++ b/src/icons/nav-bar/table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/theme-icon.svg b/src/icons/nav-bar/theme-icon.svg
new file mode 100644
index 0000000..49a3613
--- /dev/null
+++ b/src/icons/nav-bar/theme-icon.svg
@@ -0,0 +1,2 @@
+
diff --git a/src/icons/nav-bar/tree.svg b/src/icons/nav-bar/tree.svg
new file mode 100644
index 0000000..dd4b7dd
--- /dev/null
+++ b/src/icons/nav-bar/tree.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/nav-bar/user.svg b/src/icons/nav-bar/user.svg
new file mode 100644
index 0000000..0ba0716
--- /dev/null
+++ b/src/icons/nav-bar/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/lang/en.ts b/src/lang/en.ts
new file mode 100644
index 0000000..5d08966
--- /dev/null
+++ b/src/lang/en.ts
@@ -0,0 +1,123 @@
+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': '',
+ 'Second KeepAlive': '',
+ 'Tab KeepAlive': '',
+ 'Third KeepAlive': '',
+ SecondChild: '',
+ ThirdChild: '',
+
+ 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: '确 定'
+ }
+}
diff --git a/src/lang/index.ts b/src/lang/index.ts
new file mode 100644
index 0000000..d052994
--- /dev/null
+++ b/src/lang/index.ts
@@ -0,0 +1,19 @@
+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)
+ }
+}
diff --git a/src/lang/zh.ts b/src/lang/zh.ts
new file mode 100644
index 0000000..fb18a4f
--- /dev/null
+++ b/src/lang/zh.ts
@@ -0,0 +1,177 @@
+export default {
+ router: {
+ Dashboard: '首页',
+ LowCodePlatFrom: '低代码平台',
+ RBAC: '用户权限角色',
+ Guide: '指引',
+ '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': '父子组件通信',
+ 'Second KeepAlive': '二级路由缓存',
+ 'Tab KeepAlive': 'tab缓存',
+ 'Third KeepAlive': '三级路由缓存',
+ SecondChild: '三级路由示例1',
+ ThirdChild: '三级路由示例2',
+ Worker: '多线程',
+ Permission: '权限路由',
+ 'Permission Switch': '权限切换',
+ 'Role Index': '角色权限',
+ 'Code Index': 'Code权限',
+ 'Button Permission': '按钮权限',
+
+ Charts: '图表',
+ Directive: '指令',
+ 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'
+ // }
+}
diff --git a/src/layout/app-main/Breadcrumb.vue b/src/layout/app-main/Breadcrumb.vue
new file mode 100644
index 0000000..8dae76c
--- /dev/null
+++ b/src/layout/app-main/Breadcrumb.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+ {{ langTitle(item.meta?.title) }}
+
+ {{ langTitle(item.meta?.title) }}
+
+
+
+
+
+
+ {{ langTitle(item.meta?.title) }}
+
+ {{ langTitle(item.meta?.title) }}
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/Hamburger.vue b/src/layout/app-main/Hamburger.vue
new file mode 100644
index 0000000..aa6b6ca
--- /dev/null
+++ b/src/layout/app-main/Hamburger.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/Navbar.vue b/src/layout/app-main/Navbar.vue
new file mode 100644
index 0000000..aa1aa51
--- /dev/null
+++ b/src/layout/app-main/Navbar.vue
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
{{ settings.title }}
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/TagsView.vue b/src/layout/app-main/TagsView.vue
new file mode 100644
index 0000000..b6ddfe3
--- /dev/null
+++ b/src/layout/app-main/TagsView.vue
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/component/LangSelect.vue b/src/layout/app-main/component/LangSelect.vue
new file mode 100644
index 0000000..de9b18e
--- /dev/null
+++ b/src/layout/app-main/component/LangSelect.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/component/ScreenFull.vue b/src/layout/app-main/component/ScreenFull.vue
new file mode 100644
index 0000000..f984fa5
--- /dev/null
+++ b/src/layout/app-main/component/ScreenFull.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/component/ScreenLock.vue b/src/layout/app-main/component/ScreenLock.vue
new file mode 100644
index 0000000..6c420a4
--- /dev/null
+++ b/src/layout/app-main/component/ScreenLock.vue
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/component/SizeSelect.vue b/src/layout/app-main/component/SizeSelect.vue
new file mode 100644
index 0000000..b2feaaf
--- /dev/null
+++ b/src/layout/app-main/component/SizeSelect.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/component/ThemeSelect.vue b/src/layout/app-main/component/ThemeSelect.vue
new file mode 100644
index 0000000..7affc64
--- /dev/null
+++ b/src/layout/app-main/component/ThemeSelect.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/app-main/index.vue b/src/layout/app-main/index.vue
new file mode 100644
index 0000000..c6916ba
--- /dev/null
+++ b/src/layout/app-main/index.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/index.vue b/src/layout/index.vue
new file mode 100644
index 0000000..295abbf
--- /dev/null
+++ b/src/layout/index.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
diff --git a/src/layout/sidebar/Link.vue b/src/layout/sidebar/Link.vue
new file mode 100644
index 0000000..1875bee
--- /dev/null
+++ b/src/layout/sidebar/Link.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/src/layout/sidebar/Logo.vue b/src/layout/sidebar/Logo.vue
new file mode 100644
index 0000000..2729cbc
--- /dev/null
+++ b/src/layout/sidebar/Logo.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
diff --git a/src/layout/sidebar/MenuIcon.vue b/src/layout/sidebar/MenuIcon.vue
new file mode 100644
index 0000000..fd78e1d
--- /dev/null
+++ b/src/layout/sidebar/MenuIcon.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/sidebar/SidebarItem.vue b/src/layout/sidebar/SidebarItem.vue
new file mode 100644
index 0000000..1cc500c
--- /dev/null
+++ b/src/layout/sidebar/SidebarItem.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+ {{ langTitle(onlyOneChild.meta?.title) }}
+
+
+
+
+
+
+ {{ langTitle(item.meta.title) }}
+
+
+
+
+
+
+
diff --git a/src/layout/sidebar/index.vue b/src/layout/sidebar/index.vue
new file mode 100644
index 0000000..edae7cc
--- /dev/null
+++ b/src/layout/sidebar/index.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
diff --git a/src/lib/element-plus.ts b/src/lib/element-plus.ts
new file mode 100644
index 0000000..a4c34bb
--- /dev/null
+++ b/src/lib/element-plus.ts
@@ -0,0 +1,8 @@
+import * as AllComponent from 'element-plus'
+//element-plus中按需引入会引起首次加载过慢
+const elementPlusComponentNameArr = ['ElButton']
+export default function (app) {
+ elementPlusComponentNameArr.forEach((component) => {
+ app.component(component, AllComponent[component])
+ })
+}
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..dc02059
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,52 @@
+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'
+const app = createApp(App)
+
+//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)
+
+//import vxe-table
+import VXETable from 'vxe-table'
+import 'vxe-table/lib/style.css'
+app.use(VXETable)
+
+app.mount('#app')
diff --git a/src/permission.ts b/src/permission.ts
new file mode 100644
index 0000000..a2f7bb3
--- /dev/null
+++ b/src/permission.ts
@@ -0,0 +1,50 @@
+import router from '@/router'
+import { filterAsyncRouter, progressClose, progressStart } from '@/hooks/use-permission'
+import { useBasicStore } from '@/store/basic'
+import { userInfoReq } from '@/api/user'
+import { langTitle } from '@/hooks/use-common'
+
+//路由进入前拦截
+//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()
+ //1.判断token
+ if (basicStore.token) {
+ if (to.path === '/login') {
+ return '/'
+ } else {
+ //2.判断是否获取用户信息
+ if (!basicStore.getUserInfo) {
+ try {
+ const userData = await userInfoReq()
+ //3.动态路由权限筛选
+ filterAsyncRouter(userData)
+ //4.保存用户信息到store
+ basicStore.setUserInfo(userData)
+ //5.再次执行路由跳转
+ 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()
+})
diff --git a/src/plugins/vite-plugin-setup-extend/index.ts b/src/plugins/vite-plugin-setup-extend/index.ts
new file mode 100644
index 0000000..20e200d
--- /dev/null
+++ b/src/plugins/vite-plugin-setup-extend/index.ts
@@ -0,0 +1,38 @@
+import { parse } from '@vue/compiler-sfc'
+import { render } from 'ejs'
+import type { Plugin } from 'vite'
+export default ({ inject }): Plugin => {
+ // let viteConfig
+ 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`
+}
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..3d10321
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,222 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import basicDemo from './modules/basic-demo'
+import charts from './modules/charts'
+import richText from './modules/rich-text'
+import table from './modules/table'
+import excel from './modules/excel'
+import directive from './modules/directive'
+import other from './modules/other'
+import guid from './modules/guid'
+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', elSvgIcon: 'Fold', affix: true }
+ }
+ ]
+ },
+ guid,
+
+ {
+ path: '/RBAC',
+ component: Layout,
+ children: [
+ {
+ path: 'https://github.jzfai.top/low-code-platform/#/permission-center/user-table-query',
+ meta: { title: 'RBAC', icon: 'skill' }
+ }
+ ]
+ },
+ basicDemo,
+ richText,
+ charts,
+ table,
+ directive,
+ excel,
+ other,
+ {
+ path: '/setting-switch',
+ component: Layout,
+ children: [
+ {
+ path: 'index',
+ component: () => import('@/views/setting-switch/index.vue'),
+ name: 'SettingSwitch',
+ meta: { title: 'Setting Switch', icon: 'example' }
+ }
+ ]
+ },
+ {
+ path: '/error-log',
+ component: Layout,
+ meta: { title: 'Error Log', icon: 'eye' },
+ alwaysShow: true,
+ children: [
+ {
+ path: 'error-log',
+ component: () => import('@/views/error-log/index.vue'),
+ name: 'ErrorLog',
+ meta: { title: 'Error Index' }
+ },
+ {
+ path: 'error-generator',
+ component: () => import('@/views/error-log/error-generator.vue'),
+ name: 'ErrorGenerator',
+ meta: { title: 'Error Generator' }
+ }
+ ]
+ },
+ {
+ 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' }
+ }
+ ]
+ }
+]
+
+//角色和code数组动态路由
+export const roleCodeRoutes: RouterTypes = [
+ {
+ path: '/roles-codes',
+ component: Layout,
+ redirect: '/roles-codes/page',
+ alwaysShow: true, // will always show the root menu
+ name: 'Permission',
+ meta: {
+ title: 'Permission',
+ icon: 'lock',
+ roles: ['admin', 'editor'] // you can set roles in root nav
+ },
+ children: [
+ {
+ path: 'index',
+ component: () => import('@/views/roles-codes/index.vue'),
+ name: 'RolesCodes',
+ meta: { title: 'Permission Switch' }
+ },
+ {
+ 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' }
+ }
+ ]
+ }
+]
+/**
+ * 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 !!!
+ { path: '/:catchAll(.*)', name: 'CatchAll', redirect: '/404', hidden: true }
+]
+
+const router = createRouter({
+ history: createWebHashHistory(),
+ scrollBehavior: () => ({ top: 0 }),
+ routes: constantRoutes
+})
+
+export default router
diff --git a/src/router/modules/basic-demo.ts b/src/router/modules/basic-demo.ts
new file mode 100644
index 0000000..e93584b
--- /dev/null
+++ b/src/router/modules/basic-demo.ts
@@ -0,0 +1,100 @@
+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/index.vue'),
+ name: 'SecondKeepAlive',
+ //cachePage: cachePage when page enter, default false
+ //leaveRmCachePage: remove cachePage when page leave, default false
+ meta: { title: 'Second KeepAlive', cachePage: true, leaveRmCachePage: false }
+ },
+ {
+ path: 'second-child',
+ name: 'SecondChild',
+ hidden: true,
+ component: () => import('@/views/basic-demo/keep-alive/second-child.vue'),
+ meta: { title: 'SecondChild', cachePage: true, 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', cachePage: true, 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 }
+ },
+ //third-keep-alive
+ {
+ path: 'third-keep-alive',
+ name: 'ThirdKeepAlive',
+ component: () => import('@/views/basic-demo/keep-alive/third-keep-alive.vue'),
+ //注:移除父容器页面缓存会把子页面一起移除了
+ meta: { title: 'Third KeepAlive', cachePage: true, leaveRmCachePage: false },
+ alwaysShow: true,
+ children: [
+ {
+ path: 'second-children',
+ name: 'SecondChildren',
+ component: () => import('@/views/basic-demo/keep-alive/third-children/SecondChildren.vue'),
+ meta: { title: 'SecondChild', cachePage: true, leaveRmCachePage: true }
+ },
+ {
+ path: 'third-children',
+ name: 'ThirdChildren',
+ component: () => import('@/views/basic-demo/keep-alive/third-children/ThirdChildren.vue'),
+ meta: { title: 'ThirdChild', cachePage: true, leaveRmCachePage: false }
+ }
+ ]
+ },
+ {
+ path: 'worker',
+ component: () => import('@/views/basic-demo/worker/index.vue'),
+ name: 'Worker',
+ meta: { title: 'Worker' }
+ }
+ ]
+}
+
+export default BasicDemo
diff --git a/src/router/modules/charts.ts b/src/router/modules/charts.ts
new file mode 100644
index 0000000..c8841eb
--- /dev/null
+++ b/src/router/modules/charts.ts
@@ -0,0 +1,42 @@
+/** When your routing table is too long, you can split it into small modules**/
+
+import Layout from '@/layout/index.vue'
+
+const chartsRouter = {
+ path: '/charts',
+ component: Layout,
+ redirect: 'noRedirect',
+ name: 'Charts',
+ meta: {
+ title: 'Charts',
+ icon: 'chart'
+ },
+ children: [
+ {
+ path: 'keyboard',
+ component: () => import('@/views/charts/keyboard.vue'),
+ name: 'KeyboardChart',
+ meta: { title: 'Keyboard Chart', noCache: true }
+ },
+ {
+ path: 'line',
+ component: () => import('@/views/charts/line.vue'),
+ name: 'LineChart',
+ meta: { title: 'Line Chart', noCache: true }
+ },
+ {
+ path: 'mix-chart',
+ component: () => import('@/views/charts/mix-chart.vue'),
+ name: 'MixChart',
+ meta: { title: 'Mix Chart', noCache: true }
+ },
+ {
+ path: 'echarts-demo',
+ component: () => import('@/views/charts/echarts-demo.vue'),
+ name: 'EchartsDemo',
+ meta: { title: 'Echarts Demo', noCache: true }
+ }
+ ]
+}
+
+export default chartsRouter
diff --git a/src/router/modules/directive.ts b/src/router/modules/directive.ts
new file mode 100644
index 0000000..ce26e09
--- /dev/null
+++ b/src/router/modules/directive.ts
@@ -0,0 +1,48 @@
+import Layout from '@/layout/index.vue'
+
+const directive = {
+ path: '/directive',
+ component: Layout,
+ meta: { title: 'Directive', icon: 'education' },
+ alwaysShow: true,
+ children: [
+ {
+ path: 'copy',
+ component: () => import('@/views/directive/copy.vue'),
+ name: 'copy',
+ meta: { title: 'v-copy' }
+ },
+ {
+ path: 'debounce',
+ component: () => import('@/views/directive/debounce.vue'),
+ name: 'debounce',
+ meta: { title: 'v-debounce' }
+ },
+ {
+ path: 'longpress',
+ component: () => import('@/views/directive/longpress.vue'),
+ name: 'longpress',
+ meta: { title: 'v-longpress' }
+ },
+ {
+ path: 'watermark',
+ component: () => import('@/views/directive/watermark.vue'),
+ name: 'watermark',
+ meta: { title: 'v-watermark' }
+ },
+ {
+ path: 'waves',
+ component: () => import('@/views/directive/waves.vue'),
+ name: 'waves',
+ meta: { title: 'v-waves' }
+ },
+ {
+ path: 'clickoutside',
+ component: () => import('@/views/directive/clickoutside.vue'),
+ name: 'clickoutside',
+ meta: { title: 'v-clickoutside' }
+ }
+ ]
+}
+
+export default directive
diff --git a/src/router/modules/excel.ts b/src/router/modules/excel.ts
new file mode 100644
index 0000000..d722541
--- /dev/null
+++ b/src/router/modules/excel.ts
@@ -0,0 +1,23 @@
+import Layout from '@/layout/index.vue'
+const excel = {
+ path: '/excel',
+ component: Layout,
+ meta: { title: 'EXCEL', icon: 'excel' },
+ alwaysShow: true,
+ children: [
+ {
+ path: 'exportExcel',
+ component: () => import('@/views/excel/exportExcel.vue'),
+ name: 'exportExcel',
+ meta: { title: 'exportExcel' }
+ },
+ {
+ path: 'importExcel',
+ component: () => import('@/views/excel/importExcel.vue'),
+ name: 'importExcel',
+ meta: { title: 'importExcel' }
+ }
+ ]
+}
+
+export default excel
diff --git a/src/router/modules/guid.ts b/src/router/modules/guid.ts
new file mode 100644
index 0000000..885f0e2
--- /dev/null
+++ b/src/router/modules/guid.ts
@@ -0,0 +1,14 @@
+import Layout from '@/layout/index.vue'
+const guid = {
+ path: '/guide',
+ component: Layout,
+ children: [
+ {
+ path: 'index',
+ component: () => import('@/views/guide/index.vue'),
+ name: 'Guide',
+ meta: { title: 'Guide', icon: 'guide' }
+ }
+ ]
+}
+export default guid
diff --git a/src/router/modules/other.ts b/src/router/modules/other.ts
new file mode 100644
index 0000000..31c708c
--- /dev/null
+++ b/src/router/modules/other.ts
@@ -0,0 +1,35 @@
+import Layout from '@/layout/index.vue'
+const other = {
+ path: '/other',
+ component: Layout,
+ meta: { title: 'Other', icon: 'eye-open' },
+ alwaysShow: true,
+ children: [
+ {
+ path: 'count-to',
+ component: () => import('@/views/other/count-to.vue'),
+ name: 'CountTo',
+ meta: { title: 'CountTo' }
+ },
+ {
+ path: 'd3',
+ component: () => import('@/views/other/d3/index.vue'),
+ name: 'd3',
+ meta: { title: 'd3' }
+ },
+ {
+ path: 'drag-pane',
+ component: () => import('@/views/other/drag-pane.vue'),
+ name: 'DragPane',
+ meta: { title: 'DragPane' }
+ },
+ {
+ path: 'signboard',
+ component: () => import('@/views/other/signboard/index.vue'),
+ name: 'signboard',
+ meta: { title: 'signboard' }
+ }
+ ]
+}
+
+export default other
diff --git a/src/router/modules/rich-text.ts b/src/router/modules/rich-text.ts
new file mode 100644
index 0000000..252e42f
--- /dev/null
+++ b/src/router/modules/rich-text.ts
@@ -0,0 +1,17 @@
+import Layout from '@/layout/index.vue'
+const richText = {
+ path: '/rich-text',
+ component: Layout,
+ meta: { title: 'Rich Text', icon: 'clipboard' },
+ alwaysShow: true,
+ children: [
+ {
+ path: 'tinymce',
+ name: 'Tinymce',
+ component: () => import('@/views/rich-text/TinymceExample.vue'),
+ meta: { title: 'Tinymce', icon: 'nested' }
+ }
+ ]
+}
+
+export default richText
diff --git a/src/router/modules/table.ts b/src/router/modules/table.ts
new file mode 100644
index 0000000..64b8f8e
--- /dev/null
+++ b/src/router/modules/table.ts
@@ -0,0 +1,23 @@
+import Layout from '@/layout/index.vue'
+const table = {
+ path: '/table',
+ component: Layout,
+ meta: { title: 'Table', icon: 'table' },
+ alwaysShow: true,
+ children: [
+ {
+ path: 'dynamic-table',
+ name: 'DynamicTable',
+ component: () => import('@/views/table/dynamic-table.vue'),
+ meta: { title: 'Dynamic Table', icon: 'nested' }
+ },
+ {
+ path: 'vxe-table',
+ name: 'VxeTable',
+ component: () => import('@/views/table/vxe-table.vue'),
+ meta: { title: 'Vxe Table', icon: 'nested' }
+ }
+ ]
+}
+
+export default table
diff --git a/src/settings.ts b/src/settings.ts
new file mode 100644
index 0000000..f468d95
--- /dev/null
+++ b/src/settings.ts
@@ -0,0 +1,115 @@
+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: false,
+ /**
+ * @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
diff --git a/src/store/basic.ts b/src/store/basic.ts
new file mode 100644
index 0000000..d673308
--- /dev/null
+++ b/src/store/basic.ts
@@ -0,0 +1,112 @@
+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: {
+ 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)
+ })
+ },
+ setCacheViewDeep(view) {
+ this.$patch((state) => {
+ const index = state.cachedViewsDeep.indexOf(view)
+ index > -1 && state.cachedViewsDeep.splice(index, 1)
+ })
+ }
+ }
+})
diff --git a/src/store/config.ts b/src/store/config.ts
new file mode 100644
index 0000000..f4053f1
--- /dev/null
+++ b/src/store/config.ts
@@ -0,0 +1,33 @@
+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
+ }
+ }
+})
diff --git a/src/store/tags-view.ts b/src/store/tags-view.ts
new file mode 100644
index 0000000..26137cc
--- /dev/null
+++ b/src/store/tags-view.ts
@@ -0,0 +1,65 @@
+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])
+ })
+ })
+ }
+ }
+})
diff --git a/src/styles/index.scss b/src/styles/index.scss
new file mode 100644
index 0000000..ddebc39
--- /dev/null
+++ b/src/styles/index.scss
@@ -0,0 +1,69 @@
+//scss 语法糖包含常用的布局方式 flex column
+@import './scss-suger.scss';
+//重置 element-plus 样式
+@import './reset-elemenet-plus-style.scss';
+//动画文件
+@import './transition.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(100vh - #{$navBarHeight} - #{$tagViewHeight} - #{$appMainPadding * 2});
+ height: calc(100vh - #{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;
+}
diff --git a/src/styles/reset-elemenet-plus-style.scss b/src/styles/reset-elemenet-plus-style.scss
new file mode 100644
index 0000000..c7a101f
--- /dev/null
+++ b/src/styles/reset-elemenet-plus-style.scss
@@ -0,0 +1,4 @@
+//leave the padding of el-scroll sidebar
+.el-scrollbar__view {
+ padding-bottom: 80px;
+}
diff --git a/src/styles/scss-suger.scss b/src/styles/scss-suger.scss
new file mode 100644
index 0000000..677ad18
--- /dev/null
+++ b/src/styles/scss-suger.scss
@@ -0,0 +1,274 @@
+/*脱落文档流定位*/
+.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;
+}
diff --git a/src/styles/transition.scss b/src/styles/transition.scss
new file mode 100644
index 0000000..1c6400c
--- /dev/null
+++ b/src/styles/transition.scss
@@ -0,0 +1,44 @@
+// 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;
+}
diff --git a/src/theme/base/custom/ct-css-vars.scss b/src/theme/base/custom/ct-css-vars.scss
new file mode 100644
index 0000000..c97b647
--- /dev/null
+++ b/src/theme/base/custom/ct-css-vars.scss
@@ -0,0 +1,67 @@
+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: '#ddd';
+ //TagsView
+ --tags-view-background: #fff;
+ --tags-view-border-bottom-color: #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: 10px;
+ --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: #409eff;
+}
diff --git a/src/theme/base/element-plus/button.scss b/src/theme/base/element-plus/button.scss
new file mode 100644
index 0000000..af0da39
--- /dev/null
+++ b/src/theme/base/element-plus/button.scss
@@ -0,0 +1,122 @@
+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;
+ }
+}
diff --git a/src/theme/base/element-plus/checkbox.scss b/src/theme/base/element-plus/checkbox.scss
new file mode 100644
index 0000000..f47ff6c
--- /dev/null
+++ b/src/theme/base/element-plus/checkbox.scss
@@ -0,0 +1,27 @@
+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;
+ }
+}
diff --git a/src/theme/base/element-plus/css-vars.scss b/src/theme/base/element-plus/css-vars.scss
new file mode 100644
index 0000000..ed97652
--- /dev/null
+++ b/src/theme/base/element-plus/css-vars.scss
@@ -0,0 +1,17 @@
+@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;
+}
diff --git a/src/theme/base/element-plus/form.scss b/src/theme/base/element-plus/form.scss
new file mode 100644
index 0000000..663d0bb
--- /dev/null
+++ b/src/theme/base/element-plus/form.scss
@@ -0,0 +1,20 @@
+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;
+ }
+}
diff --git a/src/theme/base/element-plus/pagination.scss b/src/theme/base/element-plus/pagination.scss
new file mode 100644
index 0000000..c0fb7a2
--- /dev/null
+++ b/src/theme/base/element-plus/pagination.scss
@@ -0,0 +1,30 @@
+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);
+ }
+}
diff --git a/src/theme/base/element-plus/redio.scss b/src/theme/base/element-plus/redio.scss
new file mode 100644
index 0000000..8cf25fa
--- /dev/null
+++ b/src/theme/base/element-plus/redio.scss
@@ -0,0 +1,18 @@
+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;
+ }
+}
diff --git a/src/theme/base/element-plus/table.scss b/src/theme/base/element-plus/table.scss
new file mode 100644
index 0000000..e94a1e4
--- /dev/null
+++ b/src/theme/base/element-plus/table.scss
@@ -0,0 +1,17 @@
+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);
+ }
+}
diff --git a/src/theme/base/element-plus/var.scss b/src/theme/base/element-plus/var.scss
new file mode 100644
index 0000000..3cd7b37
--- /dev/null
+++ b/src/theme/base/element-plus/var.scss
@@ -0,0 +1,63 @@
+/* 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);
+ }
+}
diff --git a/src/theme/base/index.scss b/src/theme/base/index.scss
new file mode 100644
index 0000000..56c3150
--- /dev/null
+++ b/src/theme/base/index.scss
@@ -0,0 +1,13 @@
+/*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";
diff --git a/src/theme/china-red/custom/ct-css-vars.scss b/src/theme/china-red/custom/ct-css-vars.scss
new file mode 100644
index 0000000..17f6c47
--- /dev/null
+++ b/src/theme/china-red/custom/ct-css-vars.scss
@@ -0,0 +1,90 @@
+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: '#ddd';
+ //TagsView
+ --tags-view-background: #fff;
+ --tags-view-border-bottom-color: #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: 10px;
+ --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: #409eff;
+}
diff --git a/src/theme/china-red/element-plus/button.scss b/src/theme/china-red/element-plus/button.scss
new file mode 100644
index 0000000..93c473b
--- /dev/null
+++ b/src/theme/china-red/element-plus/button.scss
@@ -0,0 +1,123 @@
+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;
+ }
+}
diff --git a/src/theme/china-red/element-plus/checkbox.scss b/src/theme/china-red/element-plus/checkbox.scss
new file mode 100644
index 0000000..f47ff6c
--- /dev/null
+++ b/src/theme/china-red/element-plus/checkbox.scss
@@ -0,0 +1,27 @@
+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;
+ }
+}
diff --git a/src/theme/china-red/element-plus/css-vars.scss b/src/theme/china-red/element-plus/css-vars.scss
new file mode 100644
index 0000000..ed97652
--- /dev/null
+++ b/src/theme/china-red/element-plus/css-vars.scss
@@ -0,0 +1,17 @@
+@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;
+}
diff --git a/src/theme/china-red/element-plus/form.scss b/src/theme/china-red/element-plus/form.scss
new file mode 100644
index 0000000..663d0bb
--- /dev/null
+++ b/src/theme/china-red/element-plus/form.scss
@@ -0,0 +1,20 @@
+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;
+ }
+}
diff --git a/src/theme/china-red/element-plus/pagination.scss b/src/theme/china-red/element-plus/pagination.scss
new file mode 100644
index 0000000..c0fb7a2
--- /dev/null
+++ b/src/theme/china-red/element-plus/pagination.scss
@@ -0,0 +1,30 @@
+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);
+ }
+}
diff --git a/src/theme/china-red/element-plus/redio.scss b/src/theme/china-red/element-plus/redio.scss
new file mode 100644
index 0000000..8cf25fa
--- /dev/null
+++ b/src/theme/china-red/element-plus/redio.scss
@@ -0,0 +1,18 @@
+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;
+ }
+}
diff --git a/src/theme/china-red/element-plus/table.scss b/src/theme/china-red/element-plus/table.scss
new file mode 100644
index 0000000..e94a1e4
--- /dev/null
+++ b/src/theme/china-red/element-plus/table.scss
@@ -0,0 +1,17 @@
+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);
+ }
+}
diff --git a/src/theme/china-red/element-plus/var.scss b/src/theme/china-red/element-plus/var.scss
new file mode 100644
index 0000000..3cd7b37
--- /dev/null
+++ b/src/theme/china-red/element-plus/var.scss
@@ -0,0 +1,63 @@
+/* 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);
+ }
+}
diff --git a/src/theme/china-red/index.scss b/src/theme/china-red/index.scss
new file mode 100644
index 0000000..56c3150
--- /dev/null
+++ b/src/theme/china-red/index.scss
@@ -0,0 +1,13 @@
+/*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";
diff --git a/src/theme/dark/custom/ct-css-vars.scss b/src/theme/dark/custom/ct-css-vars.scss
new file mode 100644
index 0000000..772b972
--- /dev/null
+++ b/src/theme/dark/custom/ct-css-vars.scss
@@ -0,0 +1,68 @@
+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-color: #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: 10px;
+ --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: #409eff;
+}
diff --git a/src/theme/dark/element-plus/button.scss b/src/theme/dark/element-plus/button.scss
new file mode 100644
index 0000000..93c473b
--- /dev/null
+++ b/src/theme/dark/element-plus/button.scss
@@ -0,0 +1,123 @@
+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;
+ }
+}
diff --git a/src/theme/dark/element-plus/checkbox.scss b/src/theme/dark/element-plus/checkbox.scss
new file mode 100644
index 0000000..f47ff6c
--- /dev/null
+++ b/src/theme/dark/element-plus/checkbox.scss
@@ -0,0 +1,27 @@
+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;
+ }
+}
diff --git a/src/theme/dark/element-plus/css-vars.css b/src/theme/dark/element-plus/css-vars.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/theme/dark/element-plus/css-vars.css.map b/src/theme/dark/element-plus/css-vars.css.map
new file mode 100644
index 0000000..47b7af7
--- /dev/null
+++ b/src/theme/dark/element-plus/css-vars.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["var.scss","css-vars.scss","../../mixins/_var.scss"],"names":[],"mappings":"AAAA;ACMA;EACE;ECKA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA","file":"css-vars.css"}
\ No newline at end of file
diff --git a/src/theme/dark/element-plus/css-vars.scss b/src/theme/dark/element-plus/css-vars.scss
new file mode 100644
index 0000000..ed97652
--- /dev/null
+++ b/src/theme/dark/element-plus/css-vars.scss
@@ -0,0 +1,17 @@
+@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;
+}
diff --git a/src/theme/dark/element-plus/form.scss b/src/theme/dark/element-plus/form.scss
new file mode 100644
index 0000000..663d0bb
--- /dev/null
+++ b/src/theme/dark/element-plus/form.scss
@@ -0,0 +1,20 @@
+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;
+ }
+}
diff --git a/src/theme/dark/element-plus/pagination.scss b/src/theme/dark/element-plus/pagination.scss
new file mode 100644
index 0000000..c0fb7a2
--- /dev/null
+++ b/src/theme/dark/element-plus/pagination.scss
@@ -0,0 +1,30 @@
+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);
+ }
+}
diff --git a/src/theme/dark/element-plus/redio.scss b/src/theme/dark/element-plus/redio.scss
new file mode 100644
index 0000000..8cf25fa
--- /dev/null
+++ b/src/theme/dark/element-plus/redio.scss
@@ -0,0 +1,18 @@
+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;
+ }
+}
diff --git a/src/theme/dark/element-plus/table.scss b/src/theme/dark/element-plus/table.scss
new file mode 100644
index 0000000..e94a1e4
--- /dev/null
+++ b/src/theme/dark/element-plus/table.scss
@@ -0,0 +1,17 @@
+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);
+ }
+}
diff --git a/src/theme/dark/element-plus/var.scss b/src/theme/dark/element-plus/var.scss
new file mode 100644
index 0000000..3cd7b37
--- /dev/null
+++ b/src/theme/dark/element-plus/var.scss
@@ -0,0 +1,63 @@
+/* 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);
+ }
+}
diff --git a/src/theme/dark/index.scss b/src/theme/dark/index.scss
new file mode 100644
index 0000000..a2b73a3
--- /dev/null
+++ b/src/theme/dark/index.scss
@@ -0,0 +1,13 @@
+/*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";
diff --git a/src/theme/index.css b/src/theme/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/theme/index.css.map b/src/theme/index.css.map
new file mode 100644
index 0000000..9f709d1
--- /dev/null
+++ b/src/theme/index.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["base/element-plus/var.scss","base/element-plus/css-vars.scss","mixins/_var.scss","base/element-plus/button.scss","base/element-plus/checkbox.scss","base/element-plus/redio.scss","base/element-plus/pagination.scss","base/element-plus/form.scss","base/element-plus/table.scss","base/custom/ct-css-vars.scss","base/index.scss","dark/custom/ct-css-vars.scss","dark/index.scss","lighting/custom/ct-css-vars.scss","lighting/index.scss","china-red/element-plus/css-vars.scss","china-red/element-plus/button.scss","china-red/element-plus/checkbox.scss","china-red/element-plus/redio.scss","china-red/element-plus/pagination.scss","china-red/element-plus/form.scss","china-red/element-plus/table.scss","china-red/custom/ct-css-vars.scss","china-red/index.scss"],"names":[],"mappings":"AAAA;ACMA;EACE;ECKA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;;;ACZF;EACE;;AACA;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAGA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAGA;EACA;EACA;;AAGF;EACE;EACA;EACA;EAEA;EAEA;EAEA;EAGA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;;ACvHF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;;;ACvBF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;;;ACdF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;ACzBF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;;;AChBF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;ACdJ;AACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA;EAEA;EAEA;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;EAIA;EAEA;EAEA;EAEA;EAGA;;;ACjEF;ACAA;AACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACA;EAEA;EAEA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;EAIA;EAEA;EAEA;EAEA;EAGA;;;AClEF;ACAA;AACE;AAQA;EAEA;EAEA;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;EAIA;EAEA;EAEA;EAEA;EAGA;;;ACjEF;ACMA;EACE;EbKA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;;;AcZF;EACE;;AACA;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAGA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAGA;EACA;EACA;;AAGF;EACE;EACA;EACA;EAEA;EAEA;EAEA;EAGA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;;ACvHF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;EACA;;;ACvBF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;;;ACdF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;ACzBF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;;;AChBF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;ACdJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAWA;AAQA;EAEA;EAEA;EAEA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;EAIA;EAEA;EAEA;EAEA;EAGA;;AAxEA;EACE;EACA;;AAGF;EACE;EACA;;;ACvBJ","file":"index.css"}
\ No newline at end of file
diff --git a/src/theme/index.scss b/src/theme/index.scss
new file mode 100644
index 0000000..fa47ed9
--- /dev/null
+++ b/src/theme/index.scss
@@ -0,0 +1,11 @@
+// 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";
diff --git a/src/theme/lighting/custom/ct-css-vars.scss b/src/theme/lighting/custom/ct-css-vars.scss
new file mode 100644
index 0000000..7aad9d4
--- /dev/null
+++ b/src/theme/lighting/custom/ct-css-vars.scss
@@ -0,0 +1,67 @@
+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: '#ddd';
+ //TagsView
+ --tags-view-background: #fff;
+ --tags-view-border-bottom-color: #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: 10px;
+ --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: #409eff;
+}
diff --git a/src/theme/lighting/element-plus/button.scss b/src/theme/lighting/element-plus/button.scss
new file mode 100644
index 0000000..93c473b
--- /dev/null
+++ b/src/theme/lighting/element-plus/button.scss
@@ -0,0 +1,123 @@
+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;
+ }
+}
diff --git a/src/theme/lighting/element-plus/checkbox.scss b/src/theme/lighting/element-plus/checkbox.scss
new file mode 100644
index 0000000..f47ff6c
--- /dev/null
+++ b/src/theme/lighting/element-plus/checkbox.scss
@@ -0,0 +1,27 @@
+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;
+ }
+}
diff --git a/src/theme/lighting/element-plus/css-vars.css b/src/theme/lighting/element-plus/css-vars.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/theme/lighting/element-plus/css-vars.css.map b/src/theme/lighting/element-plus/css-vars.css.map
new file mode 100644
index 0000000..47b7af7
--- /dev/null
+++ b/src/theme/lighting/element-plus/css-vars.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["var.scss","css-vars.scss","../../mixins/_var.scss"],"names":[],"mappings":"AAAA;ACMA;EACE;ECKA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA","file":"css-vars.css"}
\ No newline at end of file
diff --git a/src/theme/lighting/element-plus/css-vars.scss b/src/theme/lighting/element-plus/css-vars.scss
new file mode 100644
index 0000000..2214616
--- /dev/null
+++ b/src/theme/lighting/element-plus/css-vars.scss
@@ -0,0 +1,17 @@
+@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;
+}
diff --git a/src/theme/lighting/element-plus/form.scss b/src/theme/lighting/element-plus/form.scss
new file mode 100644
index 0000000..663d0bb
--- /dev/null
+++ b/src/theme/lighting/element-plus/form.scss
@@ -0,0 +1,20 @@
+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;
+ }
+}
diff --git a/src/theme/lighting/element-plus/pagination.scss b/src/theme/lighting/element-plus/pagination.scss
new file mode 100644
index 0000000..c0fb7a2
--- /dev/null
+++ b/src/theme/lighting/element-plus/pagination.scss
@@ -0,0 +1,30 @@
+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);
+ }
+}
diff --git a/src/theme/lighting/element-plus/redio.scss b/src/theme/lighting/element-plus/redio.scss
new file mode 100644
index 0000000..8cf25fa
--- /dev/null
+++ b/src/theme/lighting/element-plus/redio.scss
@@ -0,0 +1,18 @@
+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;
+ }
+}
diff --git a/src/theme/lighting/element-plus/table.scss b/src/theme/lighting/element-plus/table.scss
new file mode 100644
index 0000000..e94a1e4
--- /dev/null
+++ b/src/theme/lighting/element-plus/table.scss
@@ -0,0 +1,17 @@
+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);
+ }
+}
diff --git a/src/theme/lighting/element-plus/var.scss b/src/theme/lighting/element-plus/var.scss
new file mode 100644
index 0000000..3cd7b37
--- /dev/null
+++ b/src/theme/lighting/element-plus/var.scss
@@ -0,0 +1,63 @@
+/* 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);
+ }
+}
diff --git a/src/theme/lighting/index.scss b/src/theme/lighting/index.scss
new file mode 100644
index 0000000..a2b73a3
--- /dev/null
+++ b/src/theme/lighting/index.scss
@@ -0,0 +1,13 @@
+/*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";
diff --git a/src/theme/mixins/_var.scss b/src/theme/mixins/_var.scss
new file mode 100644
index 0000000..1a5e4e5
--- /dev/null
+++ b/src/theme/mixins/_var.scss
@@ -0,0 +1,55 @@
+/*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});
+}
diff --git a/src/theme/mixins/config.scss b/src/theme/mixins/config.scss
new file mode 100644
index 0000000..954f652
--- /dev/null
+++ b/src/theme/mixins/config.scss
@@ -0,0 +1,5 @@
+$namespace: 'el' !default;
+$common-separator: '-' !default;
+$element-separator: '__' !default;
+$modifier-separator: '--' !default;
+$state-prefix: 'is-' !default;
diff --git a/src/theme/mixins/function.scss b/src/theme/mixins/function.scss
new file mode 100644
index 0000000..14883fc
--- /dev/null
+++ b/src/theme/mixins/function.scss
@@ -0,0 +1,60 @@
+@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;
+}
diff --git a/src/theme/mixins/mixins.scss b/src/theme/mixins/mixins.scss
new file mode 100644
index 0000000..5e2f1f9
--- /dev/null
+++ b/src/theme/mixins/mixins.scss
@@ -0,0 +1,61 @@
+//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;
+ }
+ }
+}
diff --git a/src/theme/utils/change-theme.ts b/src/theme/utils/change-theme.ts
new file mode 100644
index 0000000..aaac845
--- /dev/null
+++ b/src/theme/utils/change-theme.ts
@@ -0,0 +1,3 @@
+export const toggleHtmlClass = (className) => {
+ document.querySelectorAll('html')[0].className = className
+}
diff --git a/src/theme/utils/index.ts b/src/theme/utils/index.ts
new file mode 100644
index 0000000..b4fbcde
--- /dev/null
+++ b/src/theme/utils/index.ts
@@ -0,0 +1 @@
+export * from './change-theme'
diff --git a/src/utils/axios-req.ts b/src/utils/axios-req.ts
new file mode 100644
index 0000000..5fc3baf
--- /dev/null
+++ b/src/utils/axios-req.ts
@@ -0,0 +1,67 @@
+import axios from 'axios'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useBasicStore } from '@/store/basic'
+
+//使用axios.create()创建一个axios请求实例
+const service = axios.create()
+
+//请求前拦截
+service.interceptors.request.use(
+ (req) => {
+ const { token, axiosPromiseArr } = useBasicStore()
+ //axiosPromiseArr收集请求地址,用于取消请求
+ req.cancelToken = new axios.CancelToken((cancel) => {
+ axiosPromiseArr.push({
+ url: req.url,
+ cancel
+ })
+ })
+ //设置token到header
+ req.headers['AUTHORIZE_TOKEN'] = token
+ //如果req.method给get 请求参数设置为 ?name=xxx
+ if ('get'.includes(req.method?.toLowerCase() as string)) 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)
+ }
+)
+//导出service实例给页面调用 , config->页面的配置
+export default function axiosReq(config) {
+ return service({
+ baseURL: import.meta.env.VITE_APP_BASE_URL,
+ timeout: 8000,
+ ...config
+ })
+}
diff --git a/src/utils/bus.ts b/src/utils/bus.ts
new file mode 100644
index 0000000..1ca26c1
--- /dev/null
+++ b/src/utils/bus.ts
@@ -0,0 +1,3 @@
+//bus even
+import mitt from 'mitt'
+export default mitt()
diff --git a/src/utils/common-util.ts b/src/utils/common-util.ts
new file mode 100644
index 0000000..5be0bb6
--- /dev/null
+++ b/src/utils/common-util.ts
@@ -0,0 +1,126 @@
+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
+ }
+}
diff --git a/src/utils/mock-axios-req.ts b/src/utils/mock-axios-req.ts
new file mode 100644
index 0000000..e6f0a40
--- /dev/null
+++ b/src/utils/mock-axios-req.ts
@@ -0,0 +1,59 @@
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+
+// create an axios instance
+const service = axios.create({
+ baseURL: '', // url = base url + request url
+ // withCredentials: true, // send cookies when cross-domain requests
+ timeout: 8000 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+ (config) => {
+ return config
+ },
+ (error) => {
+ return Promise.reject(error)
+ }
+)
+
+// response interceptor
+service.interceptors.response.use(
+ /**
+ * If you want to get http information such as headers or status
+ * Please return response => response
+ */
+
+ /**
+ * Determine the request status by custom code
+ * Here is just an example
+ * You can also judge the status by HTTP Status Code
+ */
+ (response) => {
+ const res = response.data
+
+ // if the custom code is not 20000, it is judged as an error.
+ if (res.code !== 20000) {
+ ElMessage({
+ message: res.ElMessage || 'Error',
+ type: 'error',
+ duration: 5 * 1000
+ })
+ return Promise.reject(new Error(res.ElMessage || 'Error'))
+ } else {
+ return res
+ }
+ },
+ (error) => {
+ console.log(`err${error}`) // for debug
+ ElMessage({
+ message: error.ElMessage,
+ type: 'error',
+ duration: 5 * 1000
+ })
+ return Promise.reject(error)
+ }
+)
+
+export default service
diff --git a/src/views/basic-demo/hook/index.vue b/src/views/basic-demo/hook/index.vue
new file mode 100644
index 0000000..3d13e05
--- /dev/null
+++ b/src/views/basic-demo/hook/index.vue
@@ -0,0 +1,14 @@
+
+
+
引入use-common.ts中的copyValueToClipboard
+
执行hook方法
+
+
+
+
diff --git a/src/views/basic-demo/keep-alive/index.vue b/src/views/basic-demo/keep-alive/index.vue
new file mode 100644
index 0000000..377ec10
--- /dev/null
+++ b/src/views/basic-demo/keep-alive/index.vue
@@ -0,0 +1,58 @@
+
+
+
+
diff --git a/src/views/basic-demo/keep-alive/second-child.vue b/src/views/basic-demo/keep-alive/second-child.vue
new file mode 100644
index 0000000..6025f54
--- /dev/null
+++ b/src/views/basic-demo/keep-alive/second-child.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
diff --git a/src/views/basic-demo/keep-alive/tab-keep-alive.vue b/src/views/basic-demo/keep-alive/tab-keep-alive.vue
new file mode 100644
index 0000000..d9df1ef
--- /dev/null
+++ b/src/views/basic-demo/keep-alive/tab-keep-alive.vue
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/src/views/basic-demo/keep-alive/third-child.vue b/src/views/basic-demo/keep-alive/third-child.vue
new file mode 100644
index 0000000..3b5409e
--- /dev/null
+++ b/src/views/basic-demo/keep-alive/third-child.vue
@@ -0,0 +1,30 @@
+
+
+
third-level.vue
+
+
+
+
+
+
+
+
+ 返回
+
+
+
+
+
+
diff --git a/src/views/basic-demo/keep-alive/third-children/SecondChildren.vue b/src/views/basic-demo/keep-alive/third-children/SecondChildren.vue
new file mode 100644
index 0000000..7629df2
--- /dev/null
+++ b/src/views/basic-demo/keep-alive/third-children/SecondChildren.vue
@@ -0,0 +1,10 @@
+
+ I Am SecondChildren.vue
+
+
+
+
+
+
diff --git a/src/views/basic-demo/keep-alive/third-children/ThirdChildren.vue b/src/views/basic-demo/keep-alive/third-children/ThirdChildren.vue
new file mode 100644
index 0000000..236516f
--- /dev/null
+++ b/src/views/basic-demo/keep-alive/third-children/ThirdChildren.vue
@@ -0,0 +1,10 @@
+
+ I Am ThirdChildren.vue
+
+
+
+
+
+
diff --git a/src/views/basic-demo/keep-alive/third-keep-alive.vue b/src/views/basic-demo/keep-alive/third-keep-alive.vue
new file mode 100644
index 0000000..7314cb7
--- /dev/null
+++ b/src/views/basic-demo/keep-alive/third-keep-alive.vue
@@ -0,0 +1,19 @@
+
+
+
third-keep-alive demo
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/basic-demo/mock/index.vue b/src/views/basic-demo/mock/index.vue
new file mode 100644
index 0000000..cb12b67
--- /dev/null
+++ b/src/views/basic-demo/mock/index.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/src/views/basic-demo/parent-children/Children.vue b/src/views/basic-demo/parent-children/Children.vue
new file mode 100644
index 0000000..95427d6
--- /dev/null
+++ b/src/views/basic-demo/parent-children/Children.vue
@@ -0,0 +1,76 @@
+
+
+
call father method
+
emitFather
+
getFatherMethod
+
+
+
+ slot using
+
+
+
+ 没传内容
+
+
+
+ 没传header插槽
+
+
+
+ 没传footer插槽
+
+
+ v-model sync using
+ {{ childrenTitle }}
+ changeParentValue
+
+
+
diff --git a/src/views/basic-demo/parent-children/SubChildren.vue b/src/views/basic-demo/parent-children/SubChildren.vue
new file mode 100644
index 0000000..f8ff07a
--- /dev/null
+++ b/src/views/basic-demo/parent-children/SubChildren.vue
@@ -0,0 +1,34 @@
+
+ provide and inject using
+ {{ title }}
+
+ Teleport Using
+
+
+
+
+
+
+ to container
+ Close
+
+
+
+
+
+
+
+ showModalOpen
+
+
+
diff --git a/src/views/basic-demo/parent-children/index.vue b/src/views/basic-demo/parent-children/index.vue
new file mode 100644
index 0000000..a768d39
--- /dev/null
+++ b/src/views/basic-demo/parent-children/index.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
diff --git a/src/views/basic-demo/pinia/index.vue b/src/views/basic-demo/pinia/index.vue
new file mode 100644
index 0000000..79a3129
--- /dev/null
+++ b/src/views/basic-demo/pinia/index.vue
@@ -0,0 +1,14 @@
+
+
+
由于使用了 AutoImport 插件 可以直接引入pinia里的api
+
switch sidebar.opened
+
+
+
+
+
+
diff --git a/src/views/basic-demo/svg-icon/index.vue b/src/views/basic-demo/svg-icon/index.vue
new file mode 100644
index 0000000..06ae739
--- /dev/null
+++ b/src/views/basic-demo/svg-icon/index.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/src/views/basic-demo/vue3-template/Vue3Template.vue b/src/views/basic-demo/vue3-template/Vue3Template.vue
new file mode 100644
index 0000000..03bedc5
--- /dev/null
+++ b/src/views/basic-demo/vue3-template/Vue3Template.vue
@@ -0,0 +1,39 @@
+
+ vue3推荐模板可以集成在你们的vscode或webstorm中,有助于快速开发
+
+
+
+
+
diff --git a/src/views/basic-demo/worker/index.vue b/src/views/basic-demo/worker/index.vue
new file mode 100644
index 0000000..9e96667
--- /dev/null
+++ b/src/views/basic-demo/worker/index.vue
@@ -0,0 +1,45 @@
+
+
+
the recommend using way of worker
+
计算结果:{{ showPageRef }}
+
+
+
diff --git a/src/views/charts/components/Keyboard.vue b/src/views/charts/components/Keyboard.vue
new file mode 100644
index 0000000..dc0d297
--- /dev/null
+++ b/src/views/charts/components/Keyboard.vue
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
diff --git a/src/views/charts/components/LineMarker.vue b/src/views/charts/components/LineMarker.vue
new file mode 100644
index 0000000..3868df2
--- /dev/null
+++ b/src/views/charts/components/LineMarker.vue
@@ -0,0 +1,278 @@
+
+
+
+
+
+
+
diff --git a/src/views/charts/components/MixChart.vue b/src/views/charts/components/MixChart.vue
new file mode 100644
index 0000000..08b4039
--- /dev/null
+++ b/src/views/charts/components/MixChart.vue
@@ -0,0 +1,232 @@
+
+
+
+
+
diff --git a/src/views/charts/components/mixins/resize.js b/src/views/charts/components/mixins/resize.js
new file mode 100644
index 0000000..7d3eb97
--- /dev/null
+++ b/src/views/charts/components/mixins/resize.js
@@ -0,0 +1,56 @@
+import { debounce } from '@/utils'
+
+export default {
+ data() {
+ return {
+ $_sidebarElm: null,
+ $_resizeHandler: null
+ }
+ },
+ mounted() {
+ this.initListener()
+ },
+ activated() {
+ if (!this.$_resizeHandler) {
+ // avoid duplication init
+ this.initListener()
+ }
+
+ // when keep-alive chart activated, auto resize
+ this.resize()
+ },
+ beforeDestroy() {
+ this.destroyListener()
+ },
+ deactivated() {
+ this.destroyListener()
+ },
+ methods: {
+ // use $_ for mixins properties
+ // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+ $_sidebarResizeHandler(e) {
+ if (e.propertyName === 'width') {
+ this.$_resizeHandler()
+ }
+ },
+ initListener() {
+ this.$_resizeHandler = debounce(() => {
+ this.resize()
+ }, 100)
+ window.addEventListener('resize', this.$_resizeHandler)
+
+ this.$_sidebarElm = document.querySelectorAll('.sidebar-container')[0]
+ this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
+ },
+ destroyListener() {
+ window.removeEventListener('resize', this.$_resizeHandler)
+ this.$_resizeHandler = null
+
+ this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
+ },
+ resize() {
+ const { chart } = this
+ chart && chart.resize()
+ }
+ }
+}
diff --git a/src/views/charts/echarts-demo.vue b/src/views/charts/echarts-demo.vue
new file mode 100644
index 0000000..79d05b8
--- /dev/null
+++ b/src/views/charts/echarts-demo.vue
@@ -0,0 +1,356 @@
+
+
+
+
+
+
diff --git a/src/views/charts/keyboard.vue b/src/views/charts/keyboard.vue
new file mode 100644
index 0000000..82f752f
--- /dev/null
+++ b/src/views/charts/keyboard.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/charts/line.vue b/src/views/charts/line.vue
new file mode 100644
index 0000000..f4e4dc4
--- /dev/null
+++ b/src/views/charts/line.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/charts/mix-chart.vue b/src/views/charts/mix-chart.vue
new file mode 100644
index 0000000..f4c8e64
--- /dev/null
+++ b/src/views/charts/mix-chart.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue
new file mode 100644
index 0000000..af50a2d
--- /dev/null
+++ b/src/views/dashboard/index.vue
@@ -0,0 +1,51 @@
+
+
+
+
diff --git a/src/views/directive/clickoutside.vue b/src/views/directive/clickoutside.vue
new file mode 100644
index 0000000..476b635
--- /dev/null
+++ b/src/views/directive/clickoutside.vue
@@ -0,0 +1,35 @@
+
+
+
+ 点击状态:{{ sideStr }}
+
+ InSide
+
+
+
+
+
+
+
+
diff --git a/src/views/directive/copy.vue b/src/views/directive/copy.vue
new file mode 100644
index 0000000..ad1460b
--- /dev/null
+++ b/src/views/directive/copy.vue
@@ -0,0 +1,19 @@
+
+
+
+ 复制到剪切板
+
+
+
+
+
+
diff --git a/src/views/directive/debounce.vue b/src/views/directive/debounce.vue
new file mode 100644
index 0000000..f33d809
--- /dev/null
+++ b/src/views/directive/debounce.vue
@@ -0,0 +1,24 @@
+
+
+ 防抖按钮
+
+
+
+
+
+
diff --git a/src/views/directive/longpress.vue b/src/views/directive/longpress.vue
new file mode 100644
index 0000000..a43d52f
--- /dev/null
+++ b/src/views/directive/longpress.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/src/views/directive/watermark.vue b/src/views/directive/watermark.vue
new file mode 100644
index 0000000..95d2e4b
--- /dev/null
+++ b/src/views/directive/watermark.vue
@@ -0,0 +1,12 @@
+
+ 我是一个水印页面
+
+
+
+
+
diff --git a/src/views/directive/waves.vue b/src/views/directive/waves.vue
new file mode 100644
index 0000000..0d52b02
--- /dev/null
+++ b/src/views/directive/waves.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/src/views/error-log/error-generator.vue b/src/views/error-log/error-generator.vue
new file mode 100644
index 0000000..a9a7f07
--- /dev/null
+++ b/src/views/error-log/error-generator.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
diff --git a/src/views/error-log/index.vue b/src/views/error-log/index.vue
new file mode 100644
index 0000000..95b783a
--- /dev/null
+++ b/src/views/error-log/index.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ search
+ reset
+
+
+
+
+
+
+
+ multiDel
+
+
+
+
+
+
+
+ {{ row.errorLog }}}
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/error-page/401.vue b/src/views/error-page/401.vue
new file mode 100644
index 0000000..3ca5835
--- /dev/null
+++ b/src/views/error-page/401.vue
@@ -0,0 +1,93 @@
+
+
+
返回
+
+
+ Oops!
+ gif来源
+ airbnb
+ 页面
+ 你没有权限去该页面
+ 如有不满请联系你领导
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/error-page/404.vue b/src/views/error-page/404.vue
new file mode 100644
index 0000000..0a3f517
--- /dev/null
+++ b/src/views/error-page/404.vue
@@ -0,0 +1,225 @@
+
+
+
+
+
+
OOPS!
+
+
{{ message }}
+
+ Please check that the URL you entered is correct, or click the button below to return to the homepage.
+
+
Back to home
+
+
+
+
+
+
+
+
diff --git a/src/views/excel/excel.js b/src/views/excel/excel.js
new file mode 100644
index 0000000..ca0c83d
--- /dev/null
+++ b/src/views/excel/excel.js
@@ -0,0 +1,57 @@
+import * as xlsx from 'xlsx'
+const { utils, writeFile, read } = xlsx
+const DEF_FILE_NAME = 'new-excel.xlsx'
+
+function getHeaderRow(sheet) {
+ const headers = []
+ const range = utils.decode_range(sheet['!ref'])
+ let C
+ const R = range.s.r
+ /* start in the first row */
+ for (C = range.s.c; C <= range.e.c; ++C) {
+ /* walk every column in the range */
+ const cell = sheet[utils.encode_cell({ c: C, r: R })]
+ /* find the cell in the first row */
+ let hdr = `UNKNOWN ${C}` // <-- replace with your desired default
+ if (cell && cell.t) hdr = utils.format_cell(cell)
+ headers.push(hdr)
+ }
+ return headers
+}
+export function importsExcel(file) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader()
+
+ reader.onload = (e) => {
+ const data = new Uint8Array(e.target.result)
+ const workbook = read(data, { type: 'array' })
+ // console.log("workbook: ", workbook);
+
+ //将Excel 第一个sheet内容转为json格式
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]]
+ const json = utils.sheet_to_json(worksheet)
+ // console.log("jsonExcel:", jsonExcel);
+ const headers = getHeaderRow(worksheet)
+ resolve({ tableData: json, headers })
+ }
+
+ reader.readAsArrayBuffer(file.raw)
+ })
+}
+export function aoaToSheetXlsx({ data, header, filename = DEF_FILE_NAME, write2excelOpts = { bookType: 'xlsx' } }) {
+ const arrData = [...data]
+ if (header) {
+ arrData.unshift(header)
+ }
+ const worksheet = utils.aoa_to_sheet(arrData)
+ /* add worksheet to workbook */
+ const workbook = {
+ SheetNames: [filename],
+ Sheets: {
+ [filename]: worksheet
+ }
+ }
+ /* output format determined by filename */
+ writeFile(workbook, filename, write2excelOpts)
+ /* at this point, out.xlsb will have been downloaded */
+}
diff --git a/src/views/excel/exportExcel.vue b/src/views/excel/exportExcel.vue
new file mode 100644
index 0000000..5c52fdb
--- /dev/null
+++ b/src/views/excel/exportExcel.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
diff --git a/src/views/excel/importExcel.vue b/src/views/excel/importExcel.vue
new file mode 100644
index 0000000..408f8ae
--- /dev/null
+++ b/src/views/excel/importExcel.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+ 上传excel
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/guide/index.vue b/src/views/guide/index.vue
new file mode 100644
index 0000000..6dacf2f
--- /dev/null
+++ b/src/views/guide/index.vue
@@ -0,0 +1,33 @@
+
+
+
+
+ {{ t('guide.button') }}
+
+
+
+
+
diff --git a/src/views/guide/steps.ts b/src/views/guide/steps.ts
new file mode 100644
index 0000000..c285e91
--- /dev/null
+++ b/src/views/guide/steps.ts
@@ -0,0 +1,45 @@
+const steps = [
+ {
+ element: '#hamburger-container',
+ popover: {
+ title: 'Hamburger',
+ description: 'Open && Close sidebar',
+ position: 'bottom'
+ }
+ },
+ {
+ element: '#breadcrumb-container',
+ popover: {
+ title: 'Breadcrumb',
+ description: 'Indicate the current page location',
+ position: 'bottom'
+ }
+ },
+ {
+ element: '#screenfull',
+ popover: {
+ title: 'Screenfull',
+ description: 'Set the page into fullscreen',
+ position: 'left'
+ }
+ },
+ {
+ element: '#size-select',
+ popover: {
+ title: 'Switch Size',
+ description: 'Switch the system size',
+ position: 'left'
+ }
+ },
+ {
+ element: '#tags-view-container',
+ popover: {
+ title: 'Tags view',
+ description: 'The history of the page you visited',
+ position: 'bottom'
+ },
+ padding: 0
+ }
+]
+
+export default steps
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
new file mode 100644
index 0000000..93683fa
--- /dev/null
+++ b/src/views/login/index.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/src/views/login/login-alt.vue b/src/views/login/login-alt.vue
new file mode 100644
index 0000000..c266eb7
--- /dev/null
+++ b/src/views/login/login-alt.vue
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
+
+
+
+
{{ settings.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ tipMessage }}
+
+ 登录
+
+
+
+
+
+
+
+
diff --git a/src/views/login/login-basic.vue b/src/views/login/login-basic.vue
new file mode 100644
index 0000000..898d77a
--- /dev/null
+++ b/src/views/login/login-basic.vue
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
{{ settings.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ tipMessage }}
+
+ 登录
+
+
+
+
+
+
+
+
+
diff --git a/src/views/login/login-lighting.vue b/src/views/login/login-lighting.vue
new file mode 100644
index 0000000..adfadd0
--- /dev/null
+++ b/src/views/login/login-lighting.vue
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+
+
{{ settings.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ tipMessage }}
+
+ 登录
+
+
+
+
+
+
+
+
+
diff --git a/src/views/nested/menu1/index.vue b/src/views/nested/menu1/index.vue
new file mode 100644
index 0000000..a05f6e3
--- /dev/null
+++ b/src/views/nested/menu1/index.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-1/index.vue b/src/views/nested/menu1/menu1-1/index.vue
new file mode 100644
index 0000000..ca8ef65
--- /dev/null
+++ b/src/views/nested/menu1/menu1-1/index.vue
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/views/nested/menu1/menu1-2/index.vue b/src/views/nested/menu1/menu1-2/index.vue
new file mode 100644
index 0000000..1d0476c
--- /dev/null
+++ b/src/views/nested/menu1/menu1-2/index.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue b/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
new file mode 100644
index 0000000..9608e0e
--- /dev/null
+++ b/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue b/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
new file mode 100644
index 0000000..fc1f837
--- /dev/null
+++ b/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-3/index.vue b/src/views/nested/menu1/menu1-3/index.vue
new file mode 100644
index 0000000..1364e04
--- /dev/null
+++ b/src/views/nested/menu1/menu1-3/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/views/nested/menu2/index.vue b/src/views/nested/menu2/index.vue
new file mode 100644
index 0000000..44c557f
--- /dev/null
+++ b/src/views/nested/menu2/index.vue
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/views/other/count-to.vue b/src/views/other/count-to.vue
new file mode 100644
index 0000000..9a8a61b
--- /dev/null
+++ b/src/views/other/count-to.vue
@@ -0,0 +1,24 @@
+
+
+
+ {{ value.toFixed(2) }}
+
+ 开始
+
+
+
diff --git a/src/views/other/d3/component/NodeDetail.vue b/src/views/other/d3/component/NodeDetail.vue
new file mode 100644
index 0000000..cba58ee
--- /dev/null
+++ b/src/views/other/d3/component/NodeDetail.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+ {{ state.data.data[item.field] }}
+
+
+
+
+ {{ state.data.data[item.field] }}
+
+
+
+
+
+
+ {{ state.data.data[item.field] }}
+
+
+
+
+
+
+
+
+
diff --git a/src/views/other/d3/data.json b/src/views/other/d3/data.json
new file mode 100644
index 0000000..85b391d
--- /dev/null
+++ b/src/views/other/d3/data.json
@@ -0,0 +1,269 @@
+{
+ "nodes": [
+ {
+ "id": "父",
+ "color": "#dc5712",
+ "type": "gkNode",
+ "data": {
+ "id": "X202201010903",
+ "bh": "bh12224",
+ "sj": "2022-01-05 10:00:12",
+ "dl": "10A",
+ "img": "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.autohome.com.cn%2Falbum%2F2009%2F2%2F19%2Fe53e9b0b-f0bd-4a0d-b7fd-7d38924a6f69.jpg&refer=http%3A%2F%2Fimg.autohome.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1643968757&t=658b4aa47e7355dc55a5beb0e8f76639",
+ "video": "https://github.jzfai.top/file/video/test-video.mp4"
+ }
+ },
+ {
+ "id": "子",
+ "color": "#e58308",
+ "type": "DLNode",
+ "data": {
+ "id": "X202201010903",
+ "lb": "bh12224-01"
+ }
+ },
+ {
+ "id": "孙",
+ "color": "#f4d000",
+ "type": "CLNode",
+ "data": {
+ "id": "X2022010109030101",
+ "mc": "bh12224-01-01"
+ }
+ },
+ {
+ "id": "Microsoft",
+ "name": "张三",
+ "age": 18,
+ "color": "#8a977b"
+ },
+ {
+ "id": "Amazon",
+ "color": "#b6c29a"
+ },
+ {
+ "id": "HTC",
+ "color": "#407434"
+ },
+ {
+ "id": "Samsung",
+ "color": "#407434"
+ },
+ {
+ "id": "Apple",
+ "color": "#65934a"
+ },
+ {
+ "id": "Motorola",
+ "color": "#a0bf7c"
+ },
+ {
+ "id": "Nokia",
+ "color": "#407434"
+ },
+ {
+ "id": "Kodak",
+ "color": "#123555"
+ },
+ {
+ "id": "Barnes & Noble",
+ "color": "#d1484f"
+ },
+ {
+ "id": "Foxconn",
+ "color": "#e69b02"
+ },
+ {
+ "id": "Oracle",
+ "color": "#dcd0a8"
+ },
+ {
+ "id": "Google",
+ "color": "#e1eed2"
+ },
+ {
+ "id": "Inventec",
+ "color": "#e3aa25"
+ },
+ {
+ "id": "LG",
+ "color": "#a11715"
+ },
+ {
+ "id": "RIM",
+ "color": "#e5bb81"
+ },
+ {
+ "id": "Sony",
+ "color": "#a12f2f"
+ },
+ {
+ "id": "Qualcomm",
+ "color": "#e6b33d"
+ },
+ {
+ "id": "Huawei",
+ "color": "#e6b33d"
+ },
+ {
+ "id": "ZTE",
+ "color": "#a12f2f"
+ },
+ {
+ "id": "Ericsson",
+ "color": "#a12f2f"
+ }
+ ],
+ "links": [
+ {
+ "source": "父",
+ "target": "子",
+ "type": "licensing"
+ },
+ {
+ "source": "子",
+ "target": "孙",
+ "type": "licensing"
+ },
+ {
+ "source": "Microsoft",
+ "target": "Amazon",
+ "type": "licensing"
+ },
+ {
+ "source": "Microsoft",
+ "target": "HTC",
+ "type": "licensing"
+ },
+ {
+ "source": "Samsung",
+ "target": "Apple",
+ "type": "suit"
+ },
+ {
+ "source": "Motorola",
+ "target": "Apple",
+ "type": "suit"
+ },
+ {
+ "source": "Nokia",
+ "target": "Apple",
+ "type": "resolved"
+ },
+ {
+ "source": "HTC",
+ "target": "Apple",
+ "type": "suit"
+ },
+ {
+ "source": "Kodak",
+ "target": "Apple",
+ "type": "suit"
+ },
+ {
+ "source": "Microsoft",
+ "target": "Barnes & Noble",
+ "type": "suit"
+ },
+ {
+ "source": "Microsoft",
+ "target": "Foxconn",
+ "type": "suit"
+ },
+ {
+ "source": "Oracle",
+ "target": "Google",
+ "type": "suit"
+ },
+ {
+ "source": "Apple",
+ "target": "HTC",
+ "type": "suit"
+ },
+ {
+ "source": "Microsoft",
+ "target": "Inventec",
+ "type": "suit"
+ },
+ {
+ "source": "Samsung",
+ "target": "Kodak",
+ "type": "resolved"
+ },
+ {
+ "source": "LG",
+ "target": "Kodak",
+ "type": "resolved"
+ },
+ {
+ "source": "RIM",
+ "target": "Kodak",
+ "type": "suit"
+ },
+ {
+ "source": "Sony",
+ "target": "LG",
+ "type": "suit"
+ },
+ {
+ "source": "Kodak",
+ "target": "LG",
+ "type": "resolved"
+ },
+ {
+ "source": "Apple",
+ "target": "Nokia",
+ "type": "resolved"
+ },
+ {
+ "source": "Qualcomm",
+ "target": "Nokia",
+ "type": "resolved"
+ },
+ {
+ "source": "Apple",
+ "target": "Motorola",
+ "type": "suit"
+ },
+ {
+ "source": "Microsoft",
+ "target": "Motorola",
+ "type": "suit"
+ },
+ {
+ "source": "Motorola",
+ "target": "Microsoft",
+ "type": "suit"
+ },
+ {
+ "source": "Huawei",
+ "target": "ZTE",
+ "type": "suit"
+ },
+ {
+ "source": "Ericsson",
+ "target": "ZTE",
+ "type": "suit"
+ },
+ {
+ "source": "Kodak",
+ "target": "Samsung",
+ "type": "resolved"
+ },
+ {
+ "source": "Apple",
+ "target": "Samsung",
+ "type": "suit"
+ },
+ {
+ "source": "Kodak",
+ "target": "RIM",
+ "type": "suit"
+ },
+ {
+ "source": "Nokia",
+ "target": "Qualcomm",
+ "type": "suit"
+ }
+ ]
+}
diff --git a/src/views/other/d3/index.vue b/src/views/other/d3/index.vue
new file mode 100644
index 0000000..0fd43d4
--- /dev/null
+++ b/src/views/other/d3/index.vue
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 名称:{{ state.tip.content.id }}
+ 类型:{{ state.tip.content.type || '无' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/other/d3/useD3.js b/src/views/other/d3/useD3.js
new file mode 100644
index 0000000..b97cb7c
--- /dev/null
+++ b/src/views/other/d3/useD3.js
@@ -0,0 +1,183 @@
+import * as d3 from 'd3'
+
+export default function() {
+ const linkArc = (d) => {
+ const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y)
+ return `
+ M${d.source.x},${d.source.y}
+ A${r},${r} 1 0,1 ${d.target.x},${d.target.y}
+ `
+ }
+
+ // 节点拖拽方法
+ const drag = (simulation) => {
+ function dragstarted(event, d) {
+ if (!event.active) simulation.alphaTarget(0.3).restart()
+ d.fx = d.x
+ d.fy = d.y
+ }
+
+ function dragged(event, d) {
+ d.fx = event.x
+ d.fy = event.y
+ }
+
+ function dragended(event, d) {
+ if (!event.active) simulation.alphaTarget(0)
+ d.fx = null
+ d.fy = null
+ }
+
+ return d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended)
+ }
+
+ const chart = (el, opt = {}) => {
+
+ const { links, nodes } = opt.data
+ const types = Array.from(new Set(links.map((d) => d.type)))
+ // let data = { nodes: Array.from(new Set(links.flatMap((l) => [l.source, l.target])), (id) => ({ id })), links }
+ const color = d3.scaleOrdinal(types, d3.schemeCategory10) // 根据类型生成内置颜色
+ const height = opt.height || 600 // svg宽
+ const width = opt.width || 600 // svg高
+ // const nodeColor = 'rgb(19,19,19)' // 节点颜色
+ const lineColor = 'rgb(148,148,148)' // 边线颜色
+ const hoverColor = 'rgb(110,165,255)' // hover颜色
+ const showTextLength = 5 // 超出5个字后截取
+
+
+ // 生成力向图配置
+ const simulation = d3
+ .forceSimulation()
+ .nodes(nodes)
+ .force('link', d3.forceLink(links).id((d) => d.id).distance(50))
+ .force('charge', d3.forceManyBody().strength(-400))
+ .force("center",d3.forceCenter(width/2, height/2))
+ .force('x', d3.forceX())
+ .force('y', d3.forceY())
+
+
+ // 在盒子里添加svg
+ const svg = d3.select(el).append('svg')
+
+ // svg样式
+ svg.attr('width', width)
+ .attr('height', height)
+ // .attr('viewBox', [-width/2, -height/2, width, height])
+ .style('font', '12px sans-serif')
+
+ const group = svg.append('g')
+
+ // svg追加箭头
+ const marker = group
+ .append('g')
+ .selectAll('marker')
+ .data(types)
+ .join('marker')
+ .attr('id', (d) => `arrow-${d}`)
+ .attr('viewBox', '0 -5 10 10')
+ .attr('refX', 21)
+ .attr('refY',-1.5)
+ .attr('markerWidth', 8)
+ .attr('markerHeight', 8)
+ .attr('orient', 'auto')
+ .append('path')
+ // .attr('fill', color)
+ .attr('fill', lineColor)
+ .attr('d', 'M0,-5L10,0L0,5')
+
+ // svg追加边线
+ const link = group
+ .append('g')
+ .attr('fill', 'none')
+ .attr('stroke-width', 1)
+ .selectAll('path')
+ .data(links)
+ .join('path')
+ // .attr('stroke', (d) => color(d.type))
+ .attr('stroke', lineColor)
+ .attr('marker-end', (d) => `url(${new URL(`#arrow-${d.type}`, location)})`)
+ .on('click', (e) => {
+ // var rect = d3.select(this)
+ // console.log(22, e)
+ })
+
+ // svg.append("g")
+ // .selectAll("text")
+ // // .selectAll("text")
+ // .data(links)
+ // .enter()
+ // .append("text")
+ // .attr('x', (d) => (d.source.x+d.target.x)/2)
+ // .attr('y', (d) => (d.source.y+d.target.y)/2)
+ // .text((d) => '22222')
+
+ // svg追加节点
+ const node = group
+ .append('g')
+ // .attr('fill', 'black')
+ .attr('stroke-linecap', 'round')
+ .attr('stroke-linejoin', 'round')
+ .selectAll('g')
+ .data(nodes)
+ .join('g')
+ .attr('fill', (d,i) => {
+ return nodes[i].color || 'black'
+ })
+ .call(drag(simulation))
+ .on('click', opt.nodeClick)
+ .on('mouseover', opt.nodeMouseOver)
+ .on('mouseout', opt.nodMouseOut)
+
+ // 节点追加圆形
+ node
+ .append('circle')
+ .attr('stroke', 'white')
+ .attr('stroke-width', 0.5)
+ .attr('r', 10)
+ .on('mouseover', function(){
+ d3.select(this).style('fill',hoverColor)
+ })
+ .on('mouseout', function(){
+ d3.select(this).style('fill',(d,i) => {
+ return d.color || 'black'
+ })
+ })
+
+ // 节点追加圆形旁边文字
+ node
+ .append('text')
+ .attr('x', 14)
+ .attr('y', '0.31em')
+ .text((d) => d.id.slice(0,Math.max(0, showTextLength)))
+ .clone(true)
+ .lower()
+ .attr('fill', 'none')
+ .attr('stroke', 'white')
+ .attr('stroke-width', 3)
+
+
+ function zoomed(e) {
+ group.attr('transform', e.transform)
+ }
+
+ // 缩放平移配置
+ const zoom = d3.zoom()
+ .scaleExtent([0.1, 40])
+ .on('zoom', zoomed)
+
+ svg.call(zoom)
+
+ simulation.on('tick', () => {
+ link.attr('d', linkArc)
+ node.attr('transform', (d) => `translate(${d.x},${d.y})`)
+ })
+
+
+ // invalidation.then(() => simulation.stop())
+ // return svg.node()
+ return { simulation, svg }
+ }
+ return {
+ chart
+ }
+}
diff --git a/src/views/other/d3/useDatas.js b/src/views/other/d3/useDatas.js
new file mode 100644
index 0000000..3a52e0b
--- /dev/null
+++ b/src/views/other/d3/useDatas.js
@@ -0,0 +1,109 @@
+import { reactive, ref, watch } from 'vue'
+
+export default function useUserRepositories(state) {
+ const gkNode = reactive([{
+ title: '工况ID',
+ field: 'id'
+ }, {
+ title: '车辆编号',
+ field: 'bh'
+ }, {
+ title: '数据时间',
+ field: 'sj'
+ }, {
+ title: '臂架电流',
+ field: 'dl'
+ }, {
+ title: '图片',
+ field: 'img'
+ }, {
+ title: '视频',
+ field: 'video'
+ },{
+ title: '节点颜色',
+ field: 'ys'
+ }])
+
+ const gzNode = reactive([{
+ title: '故障Id',
+ field: 'id'
+ }, {
+ title: '问题类别',
+ field: 'lb'
+ }, {
+ title: '故障现象',
+ field: 'xx'
+ }, {
+ title: '故障处理方法',
+ field: 'ff'
+ }, {
+ title: '失效形式',
+ field: 'xs'
+ }, {
+ title: 'Url',
+ field: 'url'
+ }, {
+ title: '颜色',
+ field: 'ys'
+ }])
+
+ const BOMNode = reactive([{
+ title: 'BOM ID',
+ field: 'id'
+ }, {
+ title: '级别',
+ field: 'lb'
+ }, {
+ title: '编码',
+ field: 'xx'
+ }, {
+ title: '部件名称',
+ field: 'ff'
+ }, {
+ title: 'url地址',
+ field: 'xs'
+ }, {
+ title: '节点颜色',
+ field: 'url'
+ }])
+
+ const DLNode = reactive([{
+ title: '电流评价ID',
+ field: 'id'
+ }, {
+ title: '电流评价',
+ field: 'lb'
+ }, {
+ title: '节点颜色',
+ field: 'ys'
+ }])
+
+ const CLNode = reactive([{
+ title: '车辆编码',
+ field: 'id'
+ }, {
+ title: '车辆名称',
+ field: 'mc'
+ }, {
+ title: '节点颜色',
+ field: 'ys'
+ }])
+
+ const node = {
+ gkNode,
+ gzNode,
+ BOMNode,
+ DLNode,
+ CLNode
+ }
+
+
+ const nodeType = ref([])
+ watch(() => state.types, (newValue) => {
+ nodeType.value = node[newValue]
+ }, {immediate: true})
+
+ return {
+ nodeType
+ }
+}
diff --git a/src/views/other/drag-pane.vue b/src/views/other/drag-pane.vue
new file mode 100644
index 0000000..265d7a0
--- /dev/null
+++ b/src/views/other/drag-pane.vue
@@ -0,0 +1,41 @@
+
+
+
+ 1
+
+
+
+
+ 2
+
+
+ 3
+
+
+ 4
+
+
+
+
+ 5
+
+
+
+
+
+
+
diff --git a/src/views/other/signboard/component/index.vue b/src/views/other/signboard/component/index.vue
new file mode 100644
index 0000000..b8bfcad
--- /dev/null
+++ b/src/views/other/signboard/component/index.vue
@@ -0,0 +1,89 @@
+
+
+
+ {{ headerText }}
+
+
+
+
+
+
+
diff --git a/src/views/other/signboard/index.vue b/src/views/other/signboard/index.vue
new file mode 100644
index 0000000..68a91b4
--- /dev/null
+++ b/src/views/other/signboard/index.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/rbac-test/TestAddEdit.vue b/src/views/rbac-test/TestAddEdit.vue
new file mode 100644
index 0000000..6901919
--- /dev/null
+++ b/src/views/rbac-test/TestAddEdit.vue
@@ -0,0 +1,5 @@
+
+ TestAddEdit.vue
+
+
+
diff --git a/src/views/rbac-test/TestButton.vue b/src/views/rbac-test/TestButton.vue
new file mode 100644
index 0000000..cc86c33
--- /dev/null
+++ b/src/views/rbac-test/TestButton.vue
@@ -0,0 +1,14 @@
+
+
+ button code test TestTableList.vue
+
+ 按钮1
+
+
+
+ 按钮2
+
+
+
+
+
diff --git a/src/views/rbac-test/TestDetail.vue b/src/views/rbac-test/TestDetail.vue
new file mode 100644
index 0000000..6901919
--- /dev/null
+++ b/src/views/rbac-test/TestDetail.vue
@@ -0,0 +1,5 @@
+
+ TestAddEdit.vue
+
+
+
diff --git a/src/views/rbac-test/TestMenu.vue b/src/views/rbac-test/TestMenu.vue
new file mode 100644
index 0000000..fefd172
--- /dev/null
+++ b/src/views/rbac-test/TestMenu.vue
@@ -0,0 +1,22 @@
+
+
+ page test TestTableQuery.vue
+
+ toTestDetail.vue
+ TestAddEdit.vue
+
+
+
+ 去配置
+
+
+
+
+
+
+
diff --git a/src/views/redirect/index.tsx b/src/views/redirect/index.tsx
new file mode 100644
index 0000000..08e5b88
--- /dev/null
+++ b/src/views/redirect/index.tsx
@@ -0,0 +1,13 @@
+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 () =>
+ }
+})
diff --git a/src/views/rich-text/TinymceExample-bak.vue b/src/views/rich-text/TinymceExample-bak.vue
new file mode 100644
index 0000000..9c8b168
--- /dev/null
+++ b/src/views/rich-text/TinymceExample-bak.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/src/views/rich-text/TinymceExample.vue b/src/views/rich-text/TinymceExample.vue
new file mode 100644
index 0000000..707aa3e
--- /dev/null
+++ b/src/views/rich-text/TinymceExample.vue
@@ -0,0 +1,7 @@
+
+ TinymceExample.vue
+
+
+
+
+
diff --git a/src/views/rich-text/tinymce/index.vue b/src/views/rich-text/tinymce/index.vue
new file mode 100644
index 0000000..0ec18ff
--- /dev/null
+++ b/src/views/rich-text/tinymce/index.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/roles-codes/button-permission.vue b/src/views/roles-codes/button-permission.vue
new file mode 100644
index 0000000..5d1f95d
--- /dev/null
+++ b/src/views/roles-codes/button-permission.vue
@@ -0,0 +1,7 @@
+
+
+ showing when then role of admin
+ editor
+ editor and admin
+
+
diff --git a/src/views/roles-codes/code-index.vue b/src/views/roles-codes/code-index.vue
new file mode 100644
index 0000000..70d8d5d
--- /dev/null
+++ b/src/views/roles-codes/code-index.vue
@@ -0,0 +1,4 @@
+
+ CodePage.vue
+
+
diff --git a/src/views/roles-codes/index.vue b/src/views/roles-codes/index.vue
new file mode 100644
index 0000000..8aa90c8
--- /dev/null
+++ b/src/views/roles-codes/index.vue
@@ -0,0 +1,29 @@
+
+
+
notice: setting the permissionMode of settings file before you test
+
+
roles 页面权限控制
+
{{ roles }}
+
roles切换为editor
+
roles切换为admin
+
+
codes 页面权限控制
+
{{ codes }}
+
Codes切换为[1]
+
C odes切换为[16]
+
+
+
+
+
+
diff --git a/src/views/roles-codes/role-index.vue b/src/views/roles-codes/role-index.vue
new file mode 100644
index 0000000..8554d60
--- /dev/null
+++ b/src/views/roles-codes/role-index.vue
@@ -0,0 +1,3 @@
+showing when then role of admin
+
+
diff --git a/src/views/setting-switch/SettingSwitch.vue b/src/views/setting-switch/SettingSwitch.vue
new file mode 100644
index 0000000..18c7ee4
--- /dev/null
+++ b/src/views/setting-switch/SettingSwitch.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/src/views/setting-switch/index.vue b/src/views/setting-switch/index.vue
new file mode 100644
index 0000000..18c7ee4
--- /dev/null
+++ b/src/views/setting-switch/index.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/src/views/table/dynamic-table.vue b/src/views/table/dynamic-table.vue
new file mode 100644
index 0000000..93fc6f6
--- /dev/null
+++ b/src/views/table/dynamic-table.vue
@@ -0,0 +1,90 @@
+
+
+
+ {{ item.title }}
+
+
+
+
+
+ {{ scope.row[item.prop] }}
+
+
+
+
+
+
+
+
diff --git a/src/views/table/vxe-table.vue b/src/views/table/vxe-table.vue
new file mode 100644
index 0000000..7bde329
--- /dev/null
+++ b/src/views/table/vxe-table.vue
@@ -0,0 +1,368 @@
+
+
+
+
+
diff --git a/ts-out-dir/package.json b/ts-out-dir/package.json
new file mode 100644
index 0000000..28540d9
--- /dev/null
+++ b/ts-out-dir/package.json
@@ -0,0 +1,124 @@
+{
+ "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"
+ }
+}
diff --git a/ts-out-dir/src/api/user.d.ts b/ts-out-dir/src/api/user.d.ts
new file mode 100644
index 0000000..9018183
--- /dev/null
+++ b/ts-out-dir/src/api/user.d.ts
@@ -0,0 +1,3 @@
+export declare const userInfoReq: () => Promise;
+export declare const loginReq: (subForm: any) => import("axios").AxiosPromise;
+export declare const loginOutReq: () => import("axios").AxiosPromise;
diff --git a/ts-out-dir/src/api/user.js b/ts-out-dir/src/api/user.js
new file mode 100644
index 0000000..e259b7f
--- /dev/null
+++ b/ts-out-dir/src/api/user.js
@@ -0,0 +1,26 @@
+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'
+ });
+};
diff --git a/ts-out-dir/src/directives/button-codes.d.ts b/ts-out-dir/src/directives/button-codes.d.ts
new file mode 100644
index 0000000..c29f87b
--- /dev/null
+++ b/ts-out-dir/src/directives/button-codes.d.ts
@@ -0,0 +1,5 @@
+declare const _default: {
+ mounted(el: any, binding: any): void;
+ componentUpdated(el: any, binding: any): void;
+};
+export default _default;
diff --git a/ts-out-dir/src/directives/button-codes.js b/ts-out-dir/src/directives/button-codes.js
new file mode 100644
index 0000000..ce70b9e
--- /dev/null
+++ b/ts-out-dir/src/directives/button-codes.js
@@ -0,0 +1,22 @@
+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);
+ }
+};
diff --git a/ts-out-dir/src/directives/codes-permission.d.ts b/ts-out-dir/src/directives/codes-permission.d.ts
new file mode 100644
index 0000000..c29f87b
--- /dev/null
+++ b/ts-out-dir/src/directives/codes-permission.d.ts
@@ -0,0 +1,5 @@
+declare const _default: {
+ mounted(el: any, binding: any): void;
+ componentUpdated(el: any, binding: any): void;
+};
+export default _default;
diff --git a/ts-out-dir/src/directives/codes-permission.js b/ts-out-dir/src/directives/codes-permission.js
new file mode 100644
index 0000000..4c4ea18
--- /dev/null
+++ b/ts-out-dir/src/directives/codes-permission.js
@@ -0,0 +1,22 @@
+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);
+ }
+};
diff --git a/ts-out-dir/src/directives/index.d.ts b/ts-out-dir/src/directives/index.d.ts
new file mode 100644
index 0000000..74987fc
--- /dev/null
+++ b/ts-out-dir/src/directives/index.d.ts
@@ -0,0 +1 @@
+export default function (app: any): void;
diff --git a/ts-out-dir/src/directives/index.js b/ts-out-dir/src/directives/index.js
new file mode 100644
index 0000000..8f032e2
--- /dev/null
+++ b/ts-out-dir/src/directives/index.js
@@ -0,0 +1,8 @@
+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);
+}
diff --git a/ts-out-dir/src/directives/roles-permission.d.ts b/ts-out-dir/src/directives/roles-permission.d.ts
new file mode 100644
index 0000000..c29f87b
--- /dev/null
+++ b/ts-out-dir/src/directives/roles-permission.d.ts
@@ -0,0 +1,5 @@
+declare const _default: {
+ mounted(el: any, binding: any): void;
+ componentUpdated(el: any, binding: any): void;
+};
+export default _default;
diff --git a/ts-out-dir/src/directives/roles-permission.js b/ts-out-dir/src/directives/roles-permission.js
new file mode 100644
index 0000000..4735d77
--- /dev/null
+++ b/ts-out-dir/src/directives/roles-permission.js
@@ -0,0 +1,22 @@
+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);
+ }
+};
diff --git a/ts-out-dir/src/hooks/use-common.d.ts b/ts-out-dir/src/hooks/use-common.d.ts
new file mode 100644
index 0000000..1415f2f
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-common.d.ts
@@ -0,0 +1,10 @@
+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;
diff --git a/ts-out-dir/src/hooks/use-common.js b/ts-out-dir/src/hooks/use-common.js
new file mode 100644
index 0000000..d57a6a4
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-common.js
@@ -0,0 +1,31 @@
+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('复制成功');
+};
diff --git a/ts-out-dir/src/hooks/use-element.d.ts b/ts-out-dir/src/hooks/use-element.d.ts
new file mode 100644
index 0000000..8425fea
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-element.d.ts
@@ -0,0 +1,67 @@
+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;
diff --git a/ts-out-dir/src/hooks/use-element.js b/ts-out-dir/src/hooks/use-element.js
new file mode 100644
index 0000000..cff3a2f
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-element.js
@@ -0,0 +1,158 @@
+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;
+};
diff --git a/ts-out-dir/src/hooks/use-error-log.d.ts b/ts-out-dir/src/hooks/use-error-log.d.ts
new file mode 100644
index 0000000..195f730
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-error-log.d.ts
@@ -0,0 +1 @@
+export declare const useErrorLog: () => void;
diff --git a/ts-out-dir/src/hooks/use-error-log.js b/ts-out-dir/src/hooks/use-error-log.js
new file mode 100644
index 0000000..1095008
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-error-log.js
@@ -0,0 +1,28 @@
+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);
+ });
+ }
+};
diff --git a/ts-out-dir/src/hooks/use-layout.d.ts b/ts-out-dir/src/hooks/use-layout.d.ts
new file mode 100644
index 0000000..b5b118b
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-layout.d.ts
@@ -0,0 +1,2 @@
+export declare function isExternal(path: any): boolean;
+export declare function resizeHandler(): void;
diff --git a/ts-out-dir/src/hooks/use-layout.js b/ts-out-dir/src/hooks/use-layout.js
new file mode 100644
index 0000000..f473fb2
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-layout.js
@@ -0,0 +1,38 @@
+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);
+ });
+}
diff --git a/ts-out-dir/src/hooks/use-permission.d.ts b/ts-out-dir/src/hooks/use-permission.d.ts
new file mode 100644
index 0000000..fae8b27
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-permission.d.ts
@@ -0,0 +1,15 @@
+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;
diff --git a/ts-out-dir/src/hooks/use-permission.js b/ts-out-dir/src/hooks/use-permission.js
new file mode 100644
index 0000000..bf4c572
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-permission.js
@@ -0,0 +1,146 @@
+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();
+};
diff --git a/ts-out-dir/src/hooks/use-self-router.d.ts b/ts-out-dir/src/hooks/use-self-router.d.ts
new file mode 100644
index 0000000..597ef0b
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-self-router.d.ts
@@ -0,0 +1,4 @@
+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;
diff --git a/ts-out-dir/src/hooks/use-self-router.js b/ts-out-dir/src/hooks/use-self-router.js
new file mode 100644
index 0000000..adc4c4c
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-self-router.js
@@ -0,0 +1,40 @@
+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);
+};
diff --git a/ts-out-dir/src/hooks/use-table.d.ts b/ts-out-dir/src/hooks/use-table.d.ts
new file mode 100644
index 0000000..69ce9c0
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-table.d.ts
@@ -0,0 +1,15 @@
+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;
+};
diff --git a/ts-out-dir/src/hooks/use-table.js b/ts-out-dir/src/hooks/use-table.js
new file mode 100644
index 0000000..12e188f
--- /dev/null
+++ b/ts-out-dir/src/hooks/use-table.js
@@ -0,0 +1,106 @@
+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
+ };
+};
diff --git a/ts-out-dir/src/lib/element-plus.d.ts b/ts-out-dir/src/lib/element-plus.d.ts
new file mode 100644
index 0000000..74987fc
--- /dev/null
+++ b/ts-out-dir/src/lib/element-plus.d.ts
@@ -0,0 +1 @@
+export default function (app: any): void;
diff --git a/ts-out-dir/src/lib/element-plus.js b/ts-out-dir/src/lib/element-plus.js
new file mode 100644
index 0000000..daa4459
--- /dev/null
+++ b/ts-out-dir/src/lib/element-plus.js
@@ -0,0 +1,7 @@
+import * as AllComponent from 'element-plus';
+const elementPlusComponentNameArr = ['ElButton'];
+export default function (app) {
+ elementPlusComponentNameArr.forEach((component) => {
+ app.component(component, AllComponent[component]);
+ });
+}
diff --git a/ts-out-dir/src/main.d.ts b/ts-out-dir/src/main.d.ts
new file mode 100644
index 0000000..83aa19c
--- /dev/null
+++ b/ts-out-dir/src/main.d.ts
@@ -0,0 +1,6 @@
+import '@/styles/index.scss';
+import 'virtual:svg-icons-register';
+import './permission';
+import './theme/index.scss';
+import 'uno.css';
+import 'element-plus/dist/index.css';
diff --git a/ts-out-dir/src/main.js b/ts-out-dir/src/main.js
new file mode 100644
index 0000000..18a23e2
--- /dev/null
+++ b/ts-out-dir/src/main.js
@@ -0,0 +1,22 @@
+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');
diff --git a/ts-out-dir/src/permission.d.ts b/ts-out-dir/src/permission.d.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/ts-out-dir/src/permission.d.ts
@@ -0,0 +1 @@
+export {};
diff --git a/ts-out-dir/src/permission.js b/ts-out-dir/src/permission.js
new file mode 100644
index 0000000..74e3ffe
--- /dev/null
+++ b/ts-out-dir/src/permission.js
@@ -0,0 +1,44 @@
+import router from '@/router';
+import { filterAsyncRouter, progressClose, progressStart } from '@/hooks/use-permission';
+import { useBasicStore } from '@/store/basic';
+import { userInfoReq } from '@/api/user';
+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();
+});
diff --git a/ts-out-dir/src/router/index.d.ts b/ts-out-dir/src/router/index.d.ts
new file mode 100644
index 0000000..4025bef
--- /dev/null
+++ b/ts-out-dir/src/router/index.d.ts
@@ -0,0 +1,6 @@
+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;
diff --git a/ts-out-dir/src/router/index.js b/ts-out-dir/src/router/index.js
new file mode 100644
index 0000000..cace407
--- /dev/null
+++ b/ts-out-dir/src/router/index.js
@@ -0,0 +1,203 @@
+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;
diff --git a/ts-out-dir/src/settings.d.ts b/ts-out-dir/src/settings.d.ts
new file mode 100644
index 0000000..3884993
--- /dev/null
+++ b/ts-out-dir/src/settings.d.ts
@@ -0,0 +1,3 @@
+import type { SettingsConfig } from '~/basic';
+declare const settings: SettingsConfig;
+export default settings;
diff --git a/ts-out-dir/src/settings.js b/ts-out-dir/src/settings.js
new file mode 100644
index 0000000..fc38610
--- /dev/null
+++ b/ts-out-dir/src/settings.js
@@ -0,0 +1,21 @@
+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;
diff --git a/ts-out-dir/src/store/basic.d.ts b/ts-out-dir/src/store/basic.d.ts
new file mode 100644
index 0000000..aa9e833
--- /dev/null
+++ b/ts-out-dir/src/store/basic.d.ts
@@ -0,0 +1,41 @@
+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;
+}>;
diff --git a/ts-out-dir/src/store/basic.js b/ts-out-dir/src/store/basic.js
new file mode 100644
index 0000000..20bc683
--- /dev/null
+++ b/ts-out-dir/src/store/basic.js
@@ -0,0 +1,124 @@
+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);
+ }
+ }
+});
diff --git a/ts-out-dir/src/store/tagsView.d.ts b/ts-out-dir/src/store/tagsView.d.ts
new file mode 100644
index 0000000..f20ee72
--- /dev/null
+++ b/ts-out-dir/src/store/tagsView.d.ts
@@ -0,0 +1,8 @@
+export declare const useTagsViewStore: import("pinia").StoreDefinition<"tagsView", {
+ visitedViews: never[];
+}, {}, {
+ addVisitedView(view: any): void;
+ delVisitedView(view: any): Promise;
+ delOthersVisitedViews(view: any): Promise;
+ delAllVisitedViews(): Promise;
+}>;
diff --git a/ts-out-dir/src/store/tagsView.js b/ts-out-dir/src/store/tagsView.js
new file mode 100644
index 0000000..2e13710
--- /dev/null
+++ b/ts-out-dir/src/store/tagsView.js
@@ -0,0 +1,59 @@
+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]);
+ });
+ });
+ }
+ }
+});
diff --git a/ts-out-dir/src/utils/axios-req.d.ts b/ts-out-dir/src/utils/axios-req.d.ts
new file mode 100644
index 0000000..054391c
--- /dev/null
+++ b/ts-out-dir/src/utils/axios-req.d.ts
@@ -0,0 +1 @@
+export default function axiosReq(config: any): import("axios").AxiosPromise;
diff --git a/ts-out-dir/src/utils/axios-req.js b/ts-out-dir/src/utils/axios-req.js
new file mode 100644
index 0000000..0a91c23
--- /dev/null
+++ b/ts-out-dir/src/utils/axios-req.js
@@ -0,0 +1,52 @@
+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
+ });
+}
diff --git a/ts-out-dir/src/utils/bus.d.ts b/ts-out-dir/src/utils/bus.d.ts
new file mode 100644
index 0000000..f36c03c
--- /dev/null
+++ b/ts-out-dir/src/utils/bus.d.ts
@@ -0,0 +1,2 @@
+declare const _default: import("mitt").Emitter>;
+export default _default;
diff --git a/ts-out-dir/src/utils/bus.js b/ts-out-dir/src/utils/bus.js
new file mode 100644
index 0000000..4f1430d
--- /dev/null
+++ b/ts-out-dir/src/utils/bus.js
@@ -0,0 +1,2 @@
+import mitt from 'mitt';
+export default mitt();
diff --git a/ts-out-dir/src/utils/common-util.d.ts b/ts-out-dir/src/utils/common-util.d.ts
new file mode 100644
index 0000000..e6cc1fe
--- /dev/null
+++ b/ts-out-dir/src/utils/common-util.d.ts
@@ -0,0 +1,16 @@
+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;
diff --git a/ts-out-dir/src/utils/common-util.js b/ts-out-dir/src/utils/common-util.js
new file mode 100644
index 0000000..5796341
--- /dev/null
+++ b/ts-out-dir/src/utils/common-util.js
@@ -0,0 +1,66 @@
+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;
+ }
+};
diff --git a/ts-out-dir/src/views/redirect/index.d.ts b/ts-out-dir/src/views/redirect/index.d.ts
new file mode 100644
index 0000000..43bf108
--- /dev/null
+++ b/ts-out-dir/src/views/redirect/index.d.ts
@@ -0,0 +1,2 @@
+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;
diff --git a/ts-out-dir/src/views/redirect/index.jsx b/ts-out-dir/src/views/redirect/index.jsx
new file mode 100644
index 0000000..d555a02
--- /dev/null
+++ b/ts-out-dir/src/views/redirect/index.jsx
@@ -0,0 +1,13 @@
+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 () =>
;
+ }
+});
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000..85bbac9
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,50 @@
+{
+ //设置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"]
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..91851f8
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "./tsconfig.base.json",
+ "compilerOptions": {
+ "paths": {
+ "@/*": ["src/*"],
+ "~/*": ["typings/*"]
+ }
+ },
+ "include": ["src", "typings"],
+ "exclude": ["node_modules", "**/dist"]
+}
diff --git a/typings/auto-imports.d.ts b/typings/auto-imports.d.ts
new file mode 100644
index 0000000..53b92fb
--- /dev/null
+++ b/typings/auto-imports.d.ts
@@ -0,0 +1,116 @@
+// 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 clickoutside: typeof import('../src/directives/example/clickoutside.js')['default']
+ 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 copy: typeof import('../src/directives/example/copy.js')['default']
+ const copyValueToClipboard: typeof import('../src/hooks/use-common')['copyValueToClipboard']
+ const createApp: typeof import('vue')['createApp']
+ const customRef: typeof import('vue')['customRef']
+ const debounce: typeof import('../src/directives/example/debounce.js')['default']
+ 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 loginOutReq: typeof import('../src/api/user')['loginOutReq']
+ const loginReq: typeof import('../src/api/user')['loginReq']
+ const longpress: typeof import('../src/directives/example/longpress.js')['default']
+ const markRaw: typeof import('vue')['markRaw']
+ const mockAxiosReq: typeof import('../src/utils/mock-axios-req')['default']
+ 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 searchUser: typeof import('../src/api/remote-search')['searchUser']
+ 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 transactionList: typeof import('../src/api/remote-search')['transactionList']
+ 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 userInfoReq: typeof import('../src/api/user')['userInfoReq']
+ const watch: typeof import('vue')['watch']
+ const watchEffect: typeof import('vue')['watchEffect']
+ const watchPostEffect: typeof import('vue')['watchPostEffect']
+ const watchSyncEffect: typeof import('vue')['watchSyncEffect']
+ const watermark: typeof import('../src/directives/example/watermark.js')['default']
+ const waves: typeof import('../src/directives/example/waves.js')['default']
+}
diff --git a/typings/basic.d.ts b/typings/basic.d.ts
new file mode 100644
index 0000000..d322486
--- /dev/null
+++ b/typings/basic.d.ts
@@ -0,0 +1,66 @@
+/*
+ * 声明.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 {}
diff --git a/typings/components.d.ts b/typings/components.d.ts
new file mode 100644
index 0000000..c838304
--- /dev/null
+++ b/typings/components.d.ts
@@ -0,0 +1,16 @@
+// 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']
+ }
+}
diff --git a/typings/env.d.ts b/typings/env.d.ts
new file mode 100644
index 0000000..e4d89b4
--- /dev/null
+++ b/typings/env.d.ts
@@ -0,0 +1,12 @@
+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 {}
diff --git a/typings/global.d.ts b/typings/global.d.ts
new file mode 100644
index 0000000..3fe6871
--- /dev/null
+++ b/typings/global.d.ts
@@ -0,0 +1,10 @@
+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 {}
diff --git a/typings/shims-vue.d.ts b/typings/shims-vue.d.ts
new file mode 100644
index 0000000..683baad
--- /dev/null
+++ b/typings/shims-vue.d.ts
@@ -0,0 +1,6 @@
+/*fix the import warning issue of vue file*/
+declare module '*.vue' {
+ import { DefineComponent } from 'vue'
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..5b8a4c9
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,123 @@
+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'
+const prodMock = setting.openProdMock
+import vitePluginSetupExtend from './src/plugins/vite-plugin-setup-extend'
+// import { visualizer } from 'rollup-plugin-visualizer'
+const pathSrc = resolve(__dirname, 'src')
+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: 5005, // 类型: number 指定服务器端口;
+ open: false, // 类型: boolean | string在服务器启动时自动在浏览器中打开应用程序;
+ host: true,
+ https: false
+ },
+ preview: {
+ port: 5006,
+ host: true,
+ strictPort: true
+ },
+ plugins: [
+ vue({ reactivityTransform: true }),
+ 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({
+ supportTs: true,
+ mockPath: 'mock',
+ localEnabled: command === 'serve',
+ 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/api/**', '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'
+ }),
+
+ vitePluginSetupExtend({ inject: { title: setting.title } })
+ //依赖分析插件
+ // visualizer({
+ // open: true,
+ // gzipSize: true,
+ // brotliSize: true
+ // })
+ ],
+ 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']
+ }
+ }
+})
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..7924b8c
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,19 @@
+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({
+ plugins: [Vue(), VueJsx(), DefineOptions()],
+ optimizeDeps: {
+ disabled: true
+ },
+ test: {
+ clearMocks: true,
+ environment: 'jsdom',
+ //setup 文件的路径。它们将运行在每个测试文件之前。
+ setupFiles: ['./vitest.setup.ts'],
+ transformMode: {
+ web: [/\.[jt]sx$/]
+ }
+ }
+})
diff --git a/vitest.setup.ts b/vitest.setup.ts
new file mode 100644
index 0000000..8ce1df3
--- /dev/null
+++ b/vitest.setup.ts
@@ -0,0 +1,5 @@
+import { config } from '@vue/test-utils'
+import { vi } from 'vitest'
+import ResizeObserver from 'resize-observer-polyfill'
+vi.stubGlobal('ResizeObserver', ResizeObserver)
+config.global.stubs = {}