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