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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + 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 @@ + + + + + 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 @@ + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + + + 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 @@ + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + 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 @@ + + + + + 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 @@ + + + + + + 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 @@ + + + + + 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 @@ + + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + + + 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 @@ + + + + + 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 @@ + 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 @@ + + 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 @@ + + + + + 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 @@ + + + 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 @@ + + + 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 = {}