Full Code of jzfai/vue3-admin-ts for AI

master d6cc2eb2bb79 cached
212 files
277.3 KB
85.3k tokens
123 symbols
1 requests
Download .txt
Showing preview only (333K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<!DOCTYPE html>
<html lang="en" class="base-theme">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <link rel="stylesheet" href="src/styles/init-loading.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= title %></title>
  </head>
  <body>
    <div id="app">
<!--      <div class="loader-wrapper">-->
<!--        <img src="src/assets/gif/dianchi.gif"  class="loading-gif">-->
<!--        <div class="load_title">正在加载系统资源,请耐心等待</div>-->
<!--      </div>-->
    </div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>


================================================
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
================================================
<template>
  <el-config-provider :locale="lang[language]" namespace="el" :size="size">
    <router-view />
  </el-config-provider>
</template>
<script setup lang="ts">
import { onBeforeMount, onMounted } from 'vue'
//element-plus lang
import zh from 'element-plus/dist/locale/zh-cn.mjs'
import en from 'element-plus/dist/locale/en.mjs'
import { storeToRefs } from 'pinia/dist/pinia'
import { useRoute } from 'vue-router'
import { useBasicStore } from '@/store/basic'
import { useConfigStore } from '@/store/config'
import { useErrorLog } from '@/hooks/use-error-log'

//reshow default setting
import { toggleHtmlClass } from '@/theme/utils'
const lang = { zh, en }

const { settings } = storeToRefs(useBasicStore())
const { size, language } = storeToRefs(useConfigStore())
onBeforeMount(() => {
  //set tmp token when setting isNeedLogin false
  if (!settings.value.isNeedLogin) useBasicStore().setToken(settings.value.tmpToken)
})
onMounted(() => {
  //lanch the errorLog collection
  useErrorLog()
})
const route = useRoute()
onMounted(() => {
  const { setTheme, theme, setSize, size, setLanguage, language } = useConfigStore()
  setTheme(theme)
  setLanguage(language, route.meta?.title)
  setSize(size)
  toggleHtmlClass(theme)
})
</script>
<style lang="scss">
//修改进度条样式
#nprogress .bar {
  background: var(--pregress-bar-color) !important;
}
</style>


================================================
FILE: src/api/user.ts
================================================
//获取用户信息
import axiosReq from 'axios'
// export const userInfoReq = (): Promise<any> => {
//   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
================================================
<template>
  <el-icon :size="size" :color="color">
    <component :is="ElSvg[name]" />
  </el-icon>
</template>

<script setup lang="ts">
import * as ElSvg from '@element-plus/icons-vue'
const props = defineProps({
  name: {
    require: true,
    default: 'Fold',
    type: String
  },
  size: {
    require: false,
    default: 18,
    type: Number
  },
  color: {
    require: false,
    default: '',
    type: String
  }
})
</script>

<style scoped lang="scss">
//.el-svg-icon {
//  width: 1em;
//  height: 1em;
//  margin-left: -2px; //el-svg-icon has some margin
//  font-size: 20px !important;
//  text-align: left !important;
//}
</style>


================================================
FILE: src/components/TestUnit.vue
================================================
<template>
  <div>TestUnit.vue</div>
</template>

<script setup lang="ts">
const props = defineProps({
  msg: {
    require: true,
    default: 'fai',
    type: String
  }
})
</script>

<style scoped lang="scss"></style>


================================================
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(() => <ElSvgIcon name="Edit" size={30} color={'red'} />)
    // console.log(111111, wrapper.classes())
    // expect(wrapper.classes()).toContain('el-icon')
  })

  // it('icon', () => {
  //   const wrapper = mount(() => <ElSvgIcon icon={markRaw(Search)} />)
  //   expect(wrapper.findAll('Search11222')).toBeTruthy()
  // })
  //
  // it('size', () => {
  //   const wrapper = mount(() => <ElSvgIcon size="20px" />)
  //   expect(wrapper.findAll('20px')).toBeTruthy()
  // })
  //
  // it('color', () => {
  //   const wrapper = mount(() => <ElSvgIcon color={'red'} />)
  //   expect(wrapper.findAll('red')).toBeTruthy()
  // })
  //
  // it('nativeType', () => {
  //   const wrapper = mount(() => <ElSvgIcon nativeType="submit" />)
  //
  //   expect(wrapper.attributes('type')).toBe('submit')
  // })
  //
  // it('loading', () => {
  //   const wrapper = mount(() => <ElSvgIcon loading />)
  //
  //   expect(wrapper.classes()).toContain('is-loading')
  //   expect(wrapper.findComponent(Loading).exists()).toBeTruthy()
  // })
  //
  // it('size', () => {
  //   const wrapper = mount(() => <ElSvgIcon size="large" />)
  //
  //   expect(wrapper.classes()).toContain('el-button--large')
  // })
  //
  // it('plain', () => {
  //   const wrapper = mount(() => <ElSvgIcon plain />)
  //
  //   expect(wrapper.classes()).toContain('is-plain')
  // })
  //
  // it('round', () => {
  //   const wrapper = mount(() => <ElSvgIcon round />)
  //   expect(wrapper.classes()).toContain('is-round')
  // })
  //
  // it('circle', () => {
  //   const wrapper = mount(() => <ElSvgIcon circle />)
  //
  //   expect(wrapper.classes()).toContain('is-circle')
  // })

  // it('text', async () => {
  //   const bg = ref(false)
  //
  //   const wrapper = mount(() => <ElSvgIcon text bg={bg.value} />)
  //
  //   expect(wrapper.classes()).toContain('is-text')
  //
  //   bg.value = true
  //
  //   await nextTick()
  //
  //   expect(wrapper.classes()).toContain('is-has-bg')
  // })

  // it('link', async () => {
  //   const wrapper = mount(() => <ElSvgIcon link />)
  //
  //   expect(wrapper.classes()).toContain('is-link')
  // })

  // test('render text', () => {
  //   const wrapper = mount(() => (
  //     <ElSvgIcon
  //       v-slots={{
  //         default: () => AXIOM
  //       }}
  //     />
  //   ))
  //
  //   expect(wrapper.text()).toEqual(AXIOM)
  // })
  //
  // test('handle click', async () => {
  //   const wrapper = mount(() => (
  //     <ElSvgIcon
  //       v-slots={{
  //         default: () => AXIOM
  //       }}
  //     />
  //   ))
  //
  //   await wrapper.trigger('click')
  //   expect(wrapper.emitted()).toBeDefined()
  // })
  //
  // test('handle click inside', async () => {
  //   const wrapper = mount(() => (
  //     <ElSvgIcon
  //       v-slots={{
  //         default: () => <span class="inner-slot"></span>
  //       }}
  //     />
  //   ))
  //
  //   wrapper.find('.inner-slot').trigger('click')
  //   expect(wrapper.emitted()).toBeDefined()
  // })
  //
  // test('loading implies disabled', async () => {
  //   const wrapper = mount(() => (
  //     <ElSvgIcon
  //       v-slots={{
  //         default: () => AXIOM
  //       }}
  //       loading
  //     />
  //   ))
  //
  //   await wrapper.trigger('click')
  //   expect(wrapper.emitted('click')).toBeUndefined()
  // })
  //
  // it('disabled', async () => {
  //   const wrapper = mount(() => <ElSvgIcon disabled />)
  //
  //   expect(wrapper.classes()).toContain('is-disabled')
  //   await wrapper.trigger('click')
  //   expect(wrapper.emitted('click')).toBeUndefined()
  // })
  //
  // it('loading icon', () => {
  //   const wrapper = mount(() => <ElSvgIcon loadingIcon={markRaw(Search)} loading />)
  //
  //   expect(wrapper.findComponent(Search).exists()).toBeTruthy()
  // })
  //
  // it('loading slot', () => {
  //   const wrapper = mount({
  //     setup: () => () => (
  //       <ElSvgIcon v-slots={{ loading: () => <span class="custom-loading">111</span> }} loading={true}>
  //         Loading
  //       </ElSvgIcon>
  //     )
  //   })
  //
  //   expect(wrapper.find('.custom-loading').exists()).toBeTruthy()
  // })
})
// describe('ElSvgIcon Group', () => {
//   it('create', () => {
//     const wrapper = mount({
//       setup: () => () =>
//         (
//           <ButtonGroup>
//             <ElSvgIcon type="primary">Prev</ElSvgIcon>
//             <ElSvgIcon type="primary">Next</ElSvgIcon>
//           </ButtonGroup>
//         )
//     })
//     expect(wrapper.classes()).toContain('el-button-group')
//     expect(wrapper.findAll('button').length).toBe(2)
//   })
//
//   it('button group reactive size', async () => {
//     const size = ref<ComponentSize>('small')
//     const wrapper = mount({
//       setup: () => () =>
//         (
//           <ButtonGroup size={size.value}>
//             <ElSvgIcon type="primary">Prev</ElSvgIcon>
//             <ElSvgIcon type="primary">Next</ElSvgIcon>
//           </ButtonGroup>
//         )
//     })
//     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: () => () =>
//         (
//           <ButtonGroup type="warning">
//             <ElSvgIcon type="primary">Prev</ElSvgIcon>
//             <ElSvgIcon>Next</ElSvgIcon>
//           </ButtonGroup>
//         )
//     })
//     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(() => (
//       <ElSvgIcon
//         v-slots={{
//           default: () => '中文'
//         }}
//         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(() => <ElSvgIcon autoInsertSpace>&nbsp;中文&nbsp;</ElSvgIcon>)
//
//     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<any, any, any> | 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<Number> = [] //按钮权限
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<RouteRecordName> = 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<Array<ObjKeys>>([])
  const handleSelectionChange = (val) => {
    multipleSelection.value = val
  }
  /*批量删除*/
  const multiDelBtnDill = (reqConfig) => {
    let rowDeleteIdArr: Array<string> = []
    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
================================================
<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" :fill="color" />
  </svg>
</template>

<script setup lang="ts">
import { computed, defineComponent } from 'vue'
const props = defineProps({
  iconClass: {
    type: String,
    required: true
  },
  className: {
    type: String,
    default: ''
  },
  color: {
    type: String,
    default: ''
  },
})

const iconName = computed(() => `#icon-${props.iconClass}`)
const svgClass = computed(() => {
  if (props.className) {
    return `svg-icon ${props.className}`
  }
  return 'svg-icon'
})
</script>

<style scoped lang="scss">
.svg-icon {
  width: 1em;
  height: 1em;
  position: relative;
  fill: currentColor;
  vertical-align: -2px;
}
</style>


================================================
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
================================================
<template>
  <el-breadcrumb class="app-breadcrumb" separator="/">
    <!--  mainNeedAnimation:控制该面包屑是否需要动画  -->
    <transition-group v-if="settings.mainNeedAnimation" name="breadcrumb">
      <!--  根据过滤后的数组生成面包屑  -->
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
        <span v-if="item.redirect === 'noRedirect' || index === levelList.length - 1" class="no-redirect">
          {{ langTitle(item.meta?.title) }}
        </span>
        <a v-else @click.prevent="handleLink(item)">{{ langTitle(item.meta?.title) }}</a>
      </el-breadcrumb-item>
    </transition-group>
    <!--no transition-->
    <template v-else>
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
        <span v-if="item.redirect === 'noRedirect' || index === levelList.length - 1" class="no-redirect">
          {{ langTitle(item.meta?.title) }}
        </span>
        <a v-else @click.prevent="handleLink(item)">{{ langTitle(item.meta?.title) }}</a>
      </el-breadcrumb-item>
    </template>
  </el-breadcrumb>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { compile } from 'path-to-regexp'
import { useRoute, useRouter } from 'vue-router'
import type { RouterTypes } from '~/basic'
import { useBasicStore } from '@/store/basic'
import { langTitle } from '@/hooks/use-common'
const levelList = ref()
const { settings } = useBasicStore()
const route = useRoute()
const getBreadcrumb = () => {
  // only show routes with has  meta.title
  let matched: RouterTypes = route.matched.filter((item) => item.meta?.title)
  //如果首页Dashboard,如果没有,添加Dashboard路由到第一个路由
  const isHasDashboard = matched[0]?.name?.toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
  if (!isHasDashboard) {
    matched = [{ path: '/dashboard', meta: { title: 'Dashboard' } }].concat(matched)
  }
  //过滤面包屑显示的数组
  levelList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}

//页面跳转处理
//compile函数将返回一个用于将参数转换为有效路径的函数:
//const  toPath =  compile ( "/user/:id" ,  {  encode : encodeURIComponent  } ) ;
//toPath ( {  id : 123  } ) ; //=> "/user/123"
const pathCompile = (path) => {
  const { params } = route
  const toPath = compile(path)
  return toPath(params)
}
const router = useRouter()
//如果有redirect地址直接跳转,没有跳转path
const handleLink = (item) => {
  const { redirect, path } = item
  if (redirect) {
    router.push(redirect)
    return
  }
  if (path) router.push(pathCompile(path))
}
//监听路由路径刷新 面包屑显示数组
watch(
  () => route.path,
  () => getBreadcrumb(),
  { immediate: true }
)
</script>

<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 50px;
  margin-left: 8px;

  .no-redirect {
    color: var(--breadcrumb-no-redirect);
    cursor: text;
  }
}
</style>


================================================
FILE: src/layout/app-main/Hamburger.vue
================================================
<template>
  <div style="padding: 0 12px" @click="toggleClick">
    <svg-icon icon-class="hamburger" :class="{ 'is-active': isActive }" class="hamburger-style" />
  </div>
</template>

<script setup lang="ts">
import SvgIcon from '@/icons/SvgIcon.vue'
defineProps({
  isActive: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits(['toggleClick'])
//切换左侧栏关闭和隐藏
const toggleClick = () => {
  emit('toggleClick')
}
</script>

<style scoped lang="scss">
.hamburger-style {
  color: var(--hamburger-color);
  width: var(--hamburger-width);
  height: var(--hamburger-height);
  cursor: pointer;
}

.hamburger-style.is-active {
  transform: rotate(180deg);
}
</style>


================================================
FILE: src/layout/app-main/Navbar.vue
================================================
<template>
  <div class="navbar rowBC reset-el-dropdown">
    <div class="rowSC">
      <!--  切换sidebar按钮  -->
      <hamburger
        v-if="settings.showHamburger"
        :is-active="sidebar.opened"
        class="hamburger-container"
        @toggleClick="toggleSideBar"
      />
      <!--  面包屑导航  -->
      <breadcrumb class="breadcrumb-container" />
    </div>
    <!--导航标题-->
    <div v-if="settings.showNavbarTitle" class="heardCenterTitle">{{ settings.title }}</div>
    <!-- 下拉操作菜单 -->
    <div v-if="settings.ShowDropDown" class="right-menu rowSC">
      <ScreenFull />
      <ScreenLock/>
      <ThemeSelect />
      <SizeSelect />
      <LangSelect />
      <el-dropdown trigger="click" size="medium">
        <div class="avatar-wrapper">
          <img src="https://github.jzfai.top/file/images/nav-right-logo.gif" class="user-avatar" />
          <CaretBottom style="width: 1em; height: 1em; margin-left: 4px" />
        </div>
        <template #dropdown>
          <el-dropdown-menu>
            <router-link to="/">
              <el-dropdown-item>{{ langTitle('Home') }}</el-dropdown-item>
            </router-link>
            <a target="_blank" href="https://github.com/jzfai/vue3-admin-plus">
              <el-dropdown-item>{{ langTitle('Github') }}</el-dropdown-item>
            </a>
            <a target="_blank" href="https://github.jzfai.top/low-code-platform">
              <el-dropdown-item>{{ langTitle('low-code-platform') }}</el-dropdown-item>
            </a>
            <a target="_blank" href="https://github.jzfai.top/vue3-admin-doc">
              <el-dropdown-item>{{ langTitle('office-doc') }}</el-dropdown-item>
            </a>
            <!--<el-dropdown-item>修改密码</el-dropdown-item>-->
            <el-dropdown-item divided @click="loginOut">{{ langTitle('login out') }}</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>

  </div>
</template>

<script setup lang="ts">
import { nextTick } from 'vue'
import { CaretBottom } from '@element-plus/icons-vue'
import { useRoute, useRouter } from 'vue-router'
import Breadcrumb from './Breadcrumb.vue'
import Hamburger from './Hamburger.vue'
import LangSelect from './component/LangSelect.vue'
import ScreenFull from './component/ScreenFull.vue'
import SizeSelect from './component/SizeSelect.vue'
import ThemeSelect from './component/ThemeSelect.vue'
import ScreenLock from './component/ScreenLock.vue'
import { resetState } from '@/hooks/use-permission'
import { elMessage } from '@/hooks/use-element'
import { useBasicStore } from '@/store/basic'
import { langTitle } from '@/hooks/use-common'

const basicStore = useBasicStore()
const { settings, sidebar, setToggleSideBar } = basicStore
const toggleSideBar = () => {
  setToggleSideBar()
}
//退出登录
const router = useRouter()
const route = useRoute()
const loginOut = () => {
  elMessage('退出登录成功')
  router.push(`/login?redirect=${route.path}`)
  nextTick(() => {
    resetState()
  })
}
</script>

<style lang="scss" scoped>
.navbar {
  height: var(--nav-bar-height);
  overflow: hidden;
  position: relative;
  background: var(--nav-bar-background);
  box-shadow: var(--nav-bar-box-shadow);
  z-index: 0;
}

//logo
.avatar-wrapper {
  margin-top: 5px;
  position: relative;
  cursor: pointer;

  .user-avatar {
    cursor: pointer;
    width: 40px;
    height: 40px;
    border-radius: 10px;
  }

  .el-icon-caret-bottom {
    cursor: pointer;
    position: absolute;
    right: -20px;
    top: 25px;
    font-size: 12px;
  }
}

//center-title
.heardCenterTitle {
  text-align: center;
  position: absolute;
  top: 50%;
  left: 46%;
  font-weight: 600;
  font-size: 20px;
  transform: translate(-50%, -50%);
}

//drop-down
.right-menu {
  cursor: pointer;
  margin-right: 40px;
  background-color: var(--nav-bar-right-menu-background);
}
</style>


================================================
FILE: src/layout/app-main/TagsView.vue
================================================
<template>
  <div id="tags-view-container" class="tags-view-container">
    <div class="tags-view-wrapper">
      <router-link
          v-for="tag in visitedViews"
          ref="refTag"
          :key="tag.path"
          v-slot="{ navigate }"
          :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
          custom
      >
        <div
            class="tags-view-item"
            :class="isActive(tag) ? 'active' : ''"
            @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
            @contextmenu.prevent="openMenu(tag, $event)"
            @click="navigate"
        >
          {{ langTitle(tag.title) }}
          <Close v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
        </div>
      </router-link>
    </div>
    <div style="position:relative;top:-6px">
      <div v-show="visible" class="triangle" :style="{left: left + 'px'}"/>
      <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">

        <li @click="refreshSelectedTag(selectedTag)">{{ langTitle('Refresh') }}</li>
        <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">{{ langTitle('Close') }}</li>
        <li @click="closeOthersTags">{{ langTitle('Close Others') }}</li>
        <li @click="closeAllTags(selectedTag)">{{ langTitle('Close All') }}</li>
      </ul>
    </div>

  </div>
</template>

<script setup lang="ts">
import { getCurrentInstance, nextTick, onMounted, reactive, toRefs, watch } from 'vue'
import { Close } from '@element-plus/icons-vue'
import { resolve } from 'path-browserify'
import { useRoute, useRouter } from 'vue-router'
import { storeToRefs } from 'pinia/dist/pinia'
import type { RouterTypes } from '~/basic'
import { useBasicStore } from '@/store/basic'
import { useTagsViewStore } from '@/store/tags-view'
import { langTitle } from '@/hooks/use-common'
const route = useRoute()
const router = useRouter()
const state = reactive({
  visible: false,
  top: 0,
  left: 0,
  selectedTag: {},
  affixTags: [] as RouterTypes
})

const { visitedViews } = storeToRefs(useTagsViewStore())

watch(
    () => route.path,
    () => {
      addTags()
    }
)

watch(
    () => state.visible,
    (value) => {
      if (value) {
        document.body.addEventListener('click', closeMenu)
      } else {
        document.body.removeEventListener('click', closeMenu)
      }
    }
)
onMounted(() => {
  initTags()
  addTags()
})

//判断当前点击的item项,是不是当前显示的路由项,如果是则高亮
const isActive = (param) => {
  return route.path === param.path
}
//当路由设置meta.affix=true,关闭按钮消失
const isAffix = (tag) => {
  return tag.meta && tag.meta.affix
}

const filterAffixTags = (routes, basePath = '/') => {
  let tags: RouterTypes = []
  routes.forEach((route) => {
    if (route.meta && route.meta.affix) {
      const tagPath = resolve(basePath, route.path)
      tags.push({
        fullPath: tagPath,
        path: tagPath,
        name: route.name,
        meta: { ...route.meta }
      })
    }
    if (route.children) {
      const tempTags = filterAffixTags(route.children, route.path)
      if (tempTags.length >= 1) {
        tags = [...tags, ...tempTags]
      }
    }
  })
  return tags
}

//初始
const tagsViewStore = useTagsViewStore()
const { allRoutes } = useBasicStore()
const initTags = () => {
  //过滤affix=true的tags数组并赋值给state.affixTags,挂载到页面上
  const affixTags = (state.affixTags = filterAffixTags(allRoutes))
  for (const tag of affixTags) {
    // Must have tag name
    if (tag.name) {
      tagsViewStore.addVisitedView(tag)
    }
  }
}
const addTags = () => {
  if (route?.name) {
    tagsViewStore.addVisitedView(route)
  }
  return false
}

/*右键菜单部分*/
const vm = getCurrentInstance()?.proxy
//右键打开菜单
const openMenu = (tag, e) => {
  const menuMinWidth = 105
  const offsetLeft = vm?.$el.getBoundingClientRect().left // container margin left
  const offsetWidth = vm?.$el.offsetWidth // container width
  const maxLeft = offsetWidth - menuMinWidth // left boundary
  const left = e.clientX - offsetLeft + 15 // 15: margin right

  if (left > maxLeft) {
    state.left = maxLeft
  } else {
    state.left = left
  }
  state.top =16
  state.visible = true
  state.selectedTag = tag
}

const basicStore = useBasicStore()

//关闭当前标签
const closeSelectedTag = (view) => {
  tagsViewStore.delVisitedView(view).then((visitedViews) => {
    if (isActive(view)) {
      toLastView(visitedViews, view)
    }
    //remove keep-alive by the closeTabRmCache
    if (view.meta?.closeTabRmCache) {
      const routerLevel = view.matched.length
      if (routerLevel === 2) {
        basicStore.delCachedView(view.name)
      }
      if (routerLevel === 3) {
        basicStore.delCacheViewDeep(view.name)
      }
    }
  })
}

//刷新标签
const refreshSelectedTag = (view) => {
  const { fullPath } = view
  nextTick(() => {
    router.replace({
      path: `/redirect${fullPath}`
    })
  })
}

//右键关闭菜单
const closeMenu = () => {
  state.visible = false
}
//关闭其他标签
const closeOthersTags = () => {
  router.push(state.selectedTag)
  tagsViewStore.delOthersVisitedViews(state.selectedTag)
}
//关闭所有标签
const closeAllTags = (view) => {
  tagsViewStore.delAllVisitedViews().then((visitedViews) => {
    if (state.affixTags.some((tag) => tag.path === view.path)) {
      return
    }
    toLastView(visitedViews, view)
  })
}
//跳转最后一个标签
const toLastView = (visitedViews, view) => {
  //visitedViews.at(-1)获取数组最后一个元素
  const latestView = visitedViews.at(-1)
  if (latestView) {
    router.push(latestView.fullPath)
  } else {
    if (view.name === 'Dashboard') {
      // to reload home page
      router.replace({ path: `/redirect${view.fullPath}` })
    } else {
      router.push('/')
    }
  }
}

//export to page use
const { visible, top, left, selectedTag } = toRefs(state)
</script>

<style lang="scss" scoped>
//三角形汽包
.triangle {
  position: relative;
  width: 0;
  height: 0;
  left: 10px;
  z-index: 100;
  border: 8px solid transparent;
  border-bottom-color: #eee;
  opacity:0.4;
}
.tags-view-container {
  height: var(--tag-view-height);
  width: 100%;
  position: relative;
  background: var(--tags-view-background);
  border-bottom: 1px solid var(--tags-view-border-bottom);
  box-shadow: var(--tags-view-box-shadow);
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 27px;
      line-height: 26px;
      border: 1px solid var(--tags-view-item-border-color);
      color: var(--tags-view-item-color);
      background: var(--tags-view-item-background);
      padding: 0 8px;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 3px;
      &:first-of-type {
        margin-left: 10px;
      }
      &:last-of-type {
        margin-right: 15px;
      }
      &.active {
        background-color: var(--tags-view-item-active-background);
        color: var(--tags-view-item-active-color);
        border-color: var(--tags-view-item-active-border-color);
        &::before {
          content: '';
          background: var(--tags-view-background);
          display: inline-block;
          width: 8px;
          height: 8px;
          border-radius: 50%;
          position: relative;
          margin-right: 2px;
        }
      }
    }
  }
  .contextmenu {
    z-index: 100;
    margin: 0;
    background: var(--tags-view-contextmenu-background);
    position: absolute;
    list-style-type: none;
    padding: 5px 0;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: var(--tags-view-contextmenu-color);
    box-shadow: var(--tags-view-contextmenu-box-shadow);
    li {
      margin: 0;
      padding: 7px 16px;
      cursor: pointer;
      &:hover {
        background: var(--tags-view-contextmenu-hover-background);
      }
    }
  }
}
</style>

<style lang="scss">


//reset element css of el-icon-close
.tags-view-wrapper {
  .tags-view-item {
    border-radius: 3px;
    .el-icon-close {
      border-radius: 6px;
      width: 12px;
      height: 12px;
      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      transform-origin: 100% 50%;
      vertical-align: -2px;

      &:hover {
        background-color: var(--tags-view-close-icon-hover-background);
        color: var(--tags-view-close-icon-hover-color);
      }
    }
  }
}
</style>


================================================
FILE: src/layout/app-main/component/LangSelect.vue
================================================
<template>
  <el-dropdown trigger="click" type="primary" @command="handleSetLang">
    <svg-icon icon-class="language" style="width: 20px; height: 20px" class="mr-20px" />
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item
          v-for="item in langOptions"
          :key="item.value"
          :command="item.value"
          :disabled="language === item.value"
        >
          <h3 class="pt-10px pb-10px font-langPx14">{{ item.label }}</h3>
        </el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup lang="ts">
import { reactive, toRefs } from 'vue'
import { storeToRefs } from 'pinia/dist/pinia'
import { useRoute } from 'vue-router'
import SvgIcon from '@/icons/SvgIcon.vue'
import { useConfigStore } from '@/store/config'
const state = reactive({
  langOptions: [
    { label: '中文', value: 'zh' },
    { label: 'English', value: 'en' }
  ]
})
const configStore = useConfigStore()
const { language } = storeToRefs(configStore)
const route = useRoute()
const handleSetLang = (lang) => {
  //refresh i18n
  configStore.setLanguage(lang, route.meta?.title)
}
const { langOptions } = toRefs(state)
</script>

<style scoped lang="scss"></style>


================================================
FILE: src/layout/app-main/component/ScreenFull.vue
================================================
<template>
  <svg-icon
    :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
    style="width: 17px; height: 17px"
    class="mr-12px"
    @click="toggleScreen"
  />
</template>

<script setup lang="ts">
//@ts-ignore
import { onMounted, onUnmounted, reactive, toRefs } from 'vue'
import screenfull from 'screenfull'
import { ElMessage } from 'element-plus'
import SvgIcon from '@/icons/SvgIcon.vue'
const state = reactive({
  isFullscreen: false
})
onMounted(() => {
  init()
})
onUnmounted(() => {
  destroy()
})
const toggleScreen = () => {
  if (!screenfull.isEnabled) {
    ElMessage({
      message: 'you browser can not work',
      type: 'warning'
    })
    return false
  }
  screenfull.toggle()
}
const change = () => {
  state.isFullscreen = screenfull.isFullscreen
}
const init = () => {
  if (screenfull.isEnabled) {
    screenfull.on('change', change)
  }
}
const destroy = () => {
  if (screenfull.isEnabled) {
    screenfull.off('change', change)
  }
}
const { isFullscreen } = toRefs(state)
</script>

<style lang="scss" scoped>
.nav-svg-icon {
  font-size: 18px;
  color: #5a5e66;
  margin-top: 4px;
}
</style>


================================================
FILE: src/layout/app-main/component/ScreenLock.vue
================================================
<template>
  <svg-icon icon-class="lock" style="width: 18px; height: 19px" class="mr-12px" @click="open = true" />
  <Teleport to="body">
    <transition enter-active-class="screen-locker-lock" append-to-body leave-active-class="screen-locker-unlock">
      <div v-if="open" class="screen-locker">
        <div class="screen-avatar">
          <el-avatar round :size="128" src="https://github.jzfai.top/file/images/nav-right-logo.gif" />
          <div class="screen-nickname">Vue3 Admin Plus</div>
        </div>
        <div ref="slider" class="screen-slider">
          <div class="screen-locker-placeholder">滑动解锁</div>
          <div ref="sliderButton" class="screen-slider-button" @mousedown="onMousedown">
            <el-icon :size="25">
              <icon />
            </el-icon>
          </div>
        </div>
      </div>
    </transition>
  </Teleport>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { ArrowRightBold, Unlock } from '@element-plus/icons-vue'
import SvgIcon from '@/icons/SvgIcon.vue'
const open = ref(false)
const slider = ref(null)
const sliderButton = ref(null)
let startX = 0
let distance = 0
let maxDistance = 0
let minDistance = 0
const isTrigger = ref(false)

const onMousedown = (e) => {
  distance = 0
  maxDistance = 0
  minDistance = 0
  isTrigger.value = false

  sliderButton.value.style.transition = ''
  startX = e.screenX
  maxDistance = slider.value.clientWidth - sliderButton.value.clientWidth - 10
  document.addEventListener('mousemove', onMousemove)
  document.addEventListener('mouseup', onMouseup)
}

const onMousemove = (e) => {
  distance = e.screenX - startX
  if (isTrigger.value) {
    distance = maxDistance
  }
  if (distance <= minDistance) {
    distance = minDistance
  }
  if (distance >= maxDistance) {
    distance = maxDistance
    if (!isTrigger.value) {
      isTrigger.value = true
      setTimeout(() => {
        open.value = false
      }, 300)
    }
  }
  if (open.value) {
    sliderButton.value.style.transform = `translateX(${distance}px)`
  }
}

const onMouseup = () => {
  document.removeEventListener('mousemove', onMousemove)
  document.removeEventListener('mouseup', onMouseup)

  if (!isTrigger.value) {
    // 恢复原始状态
    distance = 0
    maxDistance = 0
    minDistance = 0
    isTrigger.value = false

    if (open.value) {
      sliderButton.value.style.transition = 'all 0.4s'
      sliderButton.value.style.transform = `translateX(${distance}px)`
    }
  }
}
watch(
  () => open.value,
  () => {
    if (open.value) {
      isTrigger.value = false
    }
  }
)
const icon = computed(() => {
  return isTrigger.value ? Unlock : ArrowRightBold
})
</script>

<style scoped lang="scss">
.screen-locker {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 100;
  background-color: rgba(0, 0, 0, 0.3);
  /* backdrop-filter暂不兼容firefox */
  backdrop-filter: blur(10px);
  box-shadow: 0 0 20px 5px #0000000f;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

.screen-locker-lock {
  animation: lock-down 1s ease-in-out;
}

.screen-locker-unlock {
  animation: lock-up 1s ease-in-out;
}

@keyframes lock-down {
  0% {
    transform: translate3d(0, -100%, 0);
  }
  60% {
    transform: translate3d(0, 25px, 0);
  }
  75% {
    transform: translate3d(0px, 0, 0);
  }
  90% {
    transform: translate3d(0px, 0, 0);
  }
  100% {
    transform: none;
  }
}

@keyframes lock-up {
  0% {
    transform: translate3d(0, 0px, 0);
  }
  90% {
    transform: translate3d(0px, -100%, 0);
  }
  100% {
    transform: none;
    opacity: 0;
  }
}

.screen-avatar {
  margin-bottom: 50px;
  display: flex;
  justify-content: center;
  flex-direction: column;
  align-items: center;
}

.screen-nickname {
  font-size: 30px;
  font-weight: 400;
  margin-top: 10px;
}

.screen-slider {
  width: 300px;
  height: 60px;
  border-radius: 100px;
  background-image: linear-gradient(to right, rgb(72 168 237 / 25%), rgba(255, 255, 255, 0.4), rgb(72 168 237 / 25%));
  /* 背景渐变色大小 */
  background-size: 200%;
  animation: sun 7s infinite;
  position: relative;
  box-shadow: 0 0 20px 2px rgba(0, 0, 0, 0.15);
}

.screen-slider:before {
  content: '';
  position: absolute;
  top: -6px;
  bottom: -6px;
  left: -6px;
  right: -6px;
  border-radius: 60px;
  background-image: linear-gradient(to right, rgb(72 168 237 / 25%), rgba(255, 255, 255, 0.4), rgb(72 168 237 / 25%));
  background-size: 200%;
  /* 设置模糊度 显示发光效果 */
  filter: blur(10px);
  opacity: 0.5;
  animation: sun 7s infinite;
}

.screen-slider-button {
  width: 50px;
  height: 50px;
  margin: 5px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.15);
  backdrop-filter: blur(10px);
}

.screen-locker-placeholder {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate3d(-50%, -50%, 0);
  pointer-events: none;
  opacity: 0.5;
  user-select: none;
}

@keyframes sun {
  100% {
    background-position: -400% 0;
  }
}
</style>


================================================
FILE: src/layout/app-main/component/SizeSelect.vue
================================================
<template>
  <el-dropdown id="size-select" trigger="click" type="primary" @command="handleSetSize">
    <svg-icon icon-class="size" style="width: 18px; height: 18px" class="mr-12px" />
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item
          v-for="item in sizeOptions"
          :key="item.value"
          :command="item.value"
          :disabled="size === item.value"
        >
          <h3 class="pt-10px pb-10px font-sizePx14">{{ item.label }}</h3>
        </el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup lang="ts">
import { reactive, toRefs } from 'vue'
import { storeToRefs } from 'pinia/dist/pinia'
import SvgIcon from '@/icons/SvgIcon.vue'
import { useConfigStore } from '@/store/config'
const state = reactive({
  sizeOptions: [
    { label: 'Large ', value: 'large' },
    { label: 'Default ', value: 'default' },
    { label: 'Small', value: 'small' }
  ]
})
const configStore = useConfigStore()
const { size } = storeToRefs(configStore)
const handleSetSize = (size) => {
  configStore.setSize(size)
}
const { sizeOptions } = toRefs(state)
</script>

<style scoped lang="scss"></style>


================================================
FILE: src/layout/app-main/component/ThemeSelect.vue
================================================
<template>
  <el-dropdown trigger="click" type="primary" @command="handleSetTheme">
    <svg-icon icon-class="theme-icon" style="width: 22px; height: 23px" class="mr-12px" />
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item
          v-for="item in themeOptions"
          :key="item.value"
          :command="item.value"
          :disabled="theme === item.value"
        >
          <h3 class="pt-6px pb-10px text-16px">{{ item.label }}</h3>
        </el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup lang="ts">
import { reactive, toRefs } from 'vue'
import { storeToRefs } from 'pinia/dist/pinia'
import SvgIcon from '@/icons/SvgIcon.vue'
import { useConfigStore } from '@/store/config'
const configStore = useConfigStore()

const { theme } = storeToRefs(configStore)
const state = reactive({
  themeOptions: [
    { label: 'base', value: 'base-theme' },
    { label: 'dark', value: 'dark' },
    { label: 'lighting', value: 'lighting-theme' },
    { label: 'china-red', value: 'china-red' }
  ]
})
const handleSetTheme = (theme) => {
  configStore.setTheme(theme)
}
const { themeOptions } = toRefs(state)
</script>

<style scoped lang="scss">
.theme-icon-style {
  height: 23px;
  width: 23px;
  margin-left: 8px;
  margin-right: 8px;
  color: #494949; //#ff9901;
  position: relative;
  font-weight: bold;
  top: 1px;
}
</style>


================================================
FILE: src/layout/app-main/index.vue
================================================
<template>
  <div class="app-main" :class="{ 'show-tag-view': settings.showTagsView }">
    <router-view v-slot="{ Component }">
      <!--has transition  setting by settings.mainNeedAnimation-->
      <transition v-if="settings.mainNeedAnimation" name="fade-transform" mode="out-in">
        <keep-alive :include="cachedViews">
          <component :is="Component" :key="key" />
        </keep-alive>
      </transition>
      <!-- no transition -->
      <keep-alive v-else :include="cachedViews">
        <component :is="Component" :key="key" />
      </keep-alive>
    </router-view>
  </div>
</template>

<script setup lang="ts">
import { storeToRefs } from 'pinia/dist/pinia'
import { useRoute } from 'vue-router'
import type { rawConfig } from '~/basic'
import { useBasicStore } from '@/store/basic'
import { cloneDeep } from '@/hooks/use-common'
const { settings, cachedViews } = storeToRefs(useBasicStore())
const route = useRoute()
const key = computed(() => route.path)
/*listen the component name changing, then to keep-alive the page*/
// cachePage: is true, keep-alive this Page
// leaveRmCachePage: is true, keep-alive remote when page leave
let oldRoute: rawConfig = {}
// let deepOldRouter: RouteLocationMatched | null = null
let cacheGroup: any = []
const basicStore = useBasicStore()
// const removeDeepChildren = (deepOldRouter) => {
//   deepOldRouter.children?.forEach((fItem) => {
//     basicStore.delCacheViewDeep(fItem.name)
//   })
// }
watch(
  () => route.name,
  () => {
    const routerLevel = route.matched.length

    //缓存组处理
    //first judge cacheGroup and then  remove
    if (cacheGroup.length) {
      if (!cacheGroup.includes(route.name)) {
        cacheGroup.forEach((item) => {
          basicStore.delCachedView(item)
        })
      }
    }
    //and then cache the current router config page
    if (route.meta?.cacheGroup) {
      cacheGroup = route.meta?.cacheGroup || []
      cacheGroup.forEach((fItem) => {
        basicStore.addCachedView(fItem)
      })
    }

    //二级路由处理
    if (routerLevel === 2) {
      if (oldRoute?.name) {
        if (oldRoute.meta?.leaveRmCachePage && oldRoute.meta?.cachePage) {
          basicStore.delCachedView(oldRoute.name)
        }
      }
      if (route.name) {
        if (route.meta?.cachePage) {
          basicStore.addCachedView(route.name)
        }
      }
    }
    //warning remove the third routerLevel cache func
    //三级路由处理
    // if (routerLevel === 3) {
    //   //三级时存储当前路由对象的上一级
    //   const parentRoute = route.matched[1]
    //   //否则走正常两级路由处理流程
    //   if (oldRoute?.name) {
    //     if (oldRoute.meta?.leaveRmCachePage && oldRoute.meta?.cachePage) {
    //       basicStore.delCacheViewDeep(oldRoute.name)
    //     }
    //   }
    //
    //   //取的是第二级的name
    //   if (parentRoute.name && parentRoute.meta?.cachePage) {
    //     deepOldRouter = parentRoute
    //     basicStore.addCachedView(deepOldRouter.name)
    //     if (route.name) {
    //       if (route.meta?.cachePage) {
    //         //和第三级的name进行缓存
    //         basicStore.addCachedViewDeep(route.name)
    //       }
    //     }
    //   }
    // }
    oldRoute = cloneDeep({ name: route.name, meta: route.meta })
  },
  { immediate: true }
)
</script>

<style scoped lang="scss">
.app-main {
  padding: var(--app-main-padding);
  position: relative;
  overflow: hidden;
  background-color: var(--app-main-background);
  min-height: calc(100vh - #{var(--nav-bar-height)}) !important;
}
.show-tag-view {
  min-height: calc(100vh - #{var(--nav-bar-height)} - #{var(--tag-view-height)}) !important;
}
.fixed-header + .app-main {
  padding-top: 50px;
}
</style>


================================================
FILE: src/layout/index.vue
================================================
<template>
  <div :class="classObj" class="layout-wrapper">
    <!--left side-->
    <Sidebar v-if="settings.showLeftMenu" class="sidebar-container" />
    <!--right container-->
    <div class="main-container">
      <Navbar v-if="settings.showTopNavbar" />
      <TagsView v-if="settings.showTagsView" />
      <AppMain />
    </div>
  </div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import Sidebar from './sidebar/index.vue'
import AppMain from './app-main/index.vue'
import Navbar from './app-main/Navbar.vue'
import TagsView from './app-main/TagsView.vue'
import { useBasicStore } from '@/store/basic'
import { resizeHandler } from '@/hooks/use-layout'
const { sidebar, settings } = useBasicStore()
const classObj = computed(() => {
  return {
    closeSidebar: !sidebar.opened,
    hideSidebar: !settings.showLeftMenu
  }
})
resizeHandler()
</script>

<style lang="scss" scoped>
.main-container {
  min-height: 100%;
  transition: margin-left var(--sideBar-switch-duration);
  margin-left: var(--side-bar-width);
  position: relative;
}
.sidebar-container {
  transition: width var(--sideBar-switch-duration);
  width: var(--side-bar-width) !important;
  background-color: var(--el-menu-bg-color);
  height: 100%;
  position: fixed;
  font-size: 0;
  top: 0;
  bottom: 0;
  left: 0;
  overflow: hidden;
  border-right: 0.5px solid var(--side-bar-border-right-color);
}
.closeSidebar {
  .sidebar-container {
    width: 54px !important;
  }
  .main-container {
    margin-left: 54px !important;
  }
}
.hideSidebar {
  .sidebar-container {
    width: 0 !important;
  }
  .main-container {
    margin-left: 0;
  }
}
</style>


================================================
FILE: src/layout/sidebar/Link.vue
================================================
<template>
  <component :is="type" v-bind="linkProps(to)">
    <slot />
  </component>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { isExternal } from '@/hooks/use-layout'

const props = defineProps({
  to: { type: String, required: true }
})
//判断是否时外链,true: 使用 <a/>标签, false: <router-link/>
const type = computed(() => {
  if (isExternal(props.to)) return 'a'
  return 'router-link'
})
//判断是否时外链,true: 返回 <a/>标签跳转属性, false: 直接使用当前路径
const linkProps = (to) => {
  if (isExternal(props.to)) {
    return {
      href: to,
      target: '_blank',
      //没有rel=“noopener noreferrer”的情况下使用target=“_blank”是有安全风险,超链接a标签的rel="noopener noreferrer"属性是一种新特性,它能让网站更安全
      rel: 'noopener'
    }
  }
  return { to }
}
</script>


================================================
FILE: src/layout/sidebar/Logo.vue
================================================
<template>
  <div class="sidebar-logo-container" :class="{ collapse: collapse }">
    <transition name="sidebar-logo-fade">
      <!--  折叠显示   -->
      <router-link v-if="collapse" class="sidebar-logo-link" to="/">
        <svg-icon v-if="logo" :icon-class="logo" class="sidebar-logo" />
        <h1 v-else class="sidebar-title">{{ title }}</h1>
      </router-link>
      <!--  正常显示   -->
      <router-link v-else class="sidebar-logo-link" to="/">
        <svg-icon v-if="logo" :icon-class="logo" class="sidebar-logo" />
        <h1 class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>
</template>

<script setup lang="ts">
import { reactive, toRefs } from 'vue'
import { useBasicStore } from '@/store/basic'
import SvgIcon from '@/icons/SvgIcon.vue'
const { settings } = useBasicStore()
defineProps({
  //是否折叠
  collapse: {
    type: Boolean,
    required: true
  }
})
const state = reactive({
  title: settings.title,
  //src/icons/common/sidebar-logo.svg
  logo: 'sidebar-logo'
})
//export to page for use
const { title, logo } = toRefs(state)
</script>

<style lang="scss">
//vue3.0 过度效果更改  enter-> enter-from   leave-> leave-from
.sidebar-logo-container {
  position: relative;
  width: 100%;
  height: 50px;
  line-height: 50px;
  background: var(--sidebar-logo-background);
  padding-left: 14px;
  text-align: left;
  overflow: hidden;
  & .sidebar-logo-link {
    height: 100%;
    width: 100%;
    & .sidebar-logo {
      fill: currentColor;
      color: var(--sidebar-logo-color);
      width: var(--sidebar-logo-width);
      height: var(--sidebar-logo-height);
      vertical-align: middle;
      margin-right: 12px;
    }
    & .sidebar-title {
      display: inline-block;
      margin: 0;
      color: var(--sidebar-logo-title-color);
      font-weight: 600;
      line-height: 50px;
      font-size: 14px;
      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
      vertical-align: middle;
    }
  }
  &.collapse {
    .sidebar-logo {
      margin-right: 0;
    }
  }
}
</style>


================================================
FILE: src/layout/sidebar/MenuIcon.vue
================================================
<template>
  <!-- 如果有 elSvgIcon 显示 elSvgIcon 没有显示 icon-->
  <el-icon v-if="meta?.elSvgIcon" >
    <component :is="meta.elSvgIcon" />
  </el-icon>
  <div v-else-if="meta?.icon"  class="menu-svg-class"><svg-icon :icon-class="meta?.icon"   /></div>
</template>

<script setup lang="ts">
// import * as ElSvg from '@element-plus/icons-vue'
defineProps({
  meta: { type: Object, default: null }
})
</script>

<style scoped lang="scss">
.menu-svg-class {
  vertical-align: middle;
  margin-left: 1px;
  width: 29px;
  padding-left: 5px;
  text-align: left;
}
</style>


================================================
FILE: src/layout/sidebar/SidebarItem.vue
================================================
<template>
  <template v-if="!item.hidden">
    <template v-if="showSidebarItem(item.children, item)">
      <Link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
          <MenuIcon :meta="onlyOneChild.meta || item.meta" />
          <template #title>{{ langTitle(onlyOneChild.meta?.title) }}</template>
        </el-menu-item>
      </Link>
    </template>
    <el-sub-menu v-else :index="resolvePath(item.path)">
      <template v-if="item.meta" #title>
        <MenuIcon :meta="item.meta" />
        <span>{{ langTitle(item.meta.title) }}</span>
      </template>
      <SidebarItem
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
      />
    </el-sub-menu>
  </template>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { resolve } from 'path-browserify'
import Link from './Link.vue'
import MenuIcon from './MenuIcon.vue'
import type { RouteRawConfig } from '~/basic'
import { isExternal } from '@/hooks/use-layout'
import { langTitle } from '@/hooks/use-common'

const props = defineProps({
  //每一个router Item
  item: {
    type: Object,
    required: true
  },
  //用于判断是不是子Item,设置响应的样式
  isNest: {
    type: Boolean,
    default: false
  },
  //基础路径,用于拼接
  basePath: {
    type: String,
    default: ''
  }
})
//显示sidebarItem 的情况
const onlyOneChild = ref()
const showSidebarItem = (children = [], parent) => {
  const showingChildren = children.filter((item: RouteRawConfig) => {
    if (item.hidden) {
      return false
    } else {
      return true
    }
  })
  if (showingChildren.length === 1 && !parent?.alwaysShow) {
    onlyOneChild.value = showingChildren[0]
    return true
  }
  if (showingChildren.length === 0) {
    onlyOneChild.value = { ...parent, path: '', noChildren: true }
    return true
  }
  return false
}
const resolvePath = (routePath) => {
  if (isExternal(routePath)) {
    return routePath
  }
  if (isExternal(props.basePath)) {
    return props.basePath
  }
  return resolve(props.basePath, routePath)
}
</script>


================================================
FILE: src/layout/sidebar/index.vue
================================================
<template>
  <div id="Sidebar" class="reset-menu-style">
    <!--logo-->
    <Logo v-if="settings.sidebarLogo" :collapse="!sidebar.opened" />
    <!--router menu-->
    <el-scrollbar>
      <el-menu
        class="el-menu-vertical"
        :collapse="!sidebar.opened"
        :default-active="activeMenu"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item v-for="route in allRoutes" :key="route.path" :item="route" :base-path="route.path" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { storeToRefs } from 'pinia/dist/pinia'
import { useRoute } from 'vue-router'
import Logo from './Logo.vue'
import SidebarItem from './SidebarItem.vue'
import { useBasicStore } from '@/store/basic'
const { settings, allRoutes, sidebar } = storeToRefs(useBasicStore())
const routeInstance = useRoute()
const activeMenu = computed(() => {
  const { meta, path } = routeInstance
  // if set path, the sidebar will highlight the path you set
  if (meta.activeMenu) {
    return meta.activeMenu
  }
  return path
})
</script>
<style lang="scss">
//fix open the item style issue
.el-menu-vertical {
  width: var(--side-bar-width);
}
.reset-menu-style{
  border-right: 1px solid var(--side-bar-border-right-color);
}
</style>


================================================
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 `<script ${lang ? `lang="${lang}"` : ''}>
import { defineComponent } from 'vue'
export default defineComponent({
  ${name ? `name: "${name}",` : ''}
})
</script>\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<string>,
      codes: [] as Array<number>,
      //keep-alive
      cachedViews: [] as Array<string>,
      cachedViewsDeep: [] as Array<string>,
      //other
      sidebar: { opened: true },
      //axios req collection
      axiosPromiseArr: [] as Array<ObjKeys>,
      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 删除后的数组
Download .txt
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
Download .txt
SYMBOL INDEX (123 symbols across 30 files)

FILE: src/directives/button-codes.ts
  function checkPermission (line 3) | function checkPermission(el, { value }) {
  method mounted (line 15) | mounted(el, binding) {
  method componentUpdated (line 18) | componentUpdated(el, binding) {

FILE: src/directives/codes-permission.ts
  function checkPermission (line 2) | function checkPermission(el, { value }) {
  method mounted (line 14) | mounted(el, binding) {
  method componentUpdated (line 17) | componentUpdated(el, binding) {

FILE: src/directives/lang.ts
  function checkPermission (line 11) | function checkPermission(el, { value }) {
  method mounted (line 41) | mounted(el, binding) {

FILE: src/directives/roles-permission.ts
  function checkPermission (line 2) | function checkPermission(el, { value }) {
  method mounted (line 14) | mounted(el, binding) {
  method componentUpdated (line 17) | componentUpdated(el, binding) {

FILE: src/hooks/use-common.ts
  function cloneDeep (line 21) | function cloneDeep(value) {

FILE: src/hooks/use-layout.ts
  function isExternal (line 8) | function isExternal(path) {
  function resizeHandler (line 13) | function resizeHandler() {

FILE: src/hooks/use-permission.ts
  type menuRow (line 20) | interface menuRow {
  function filterAsyncRoutesByRoles (line 92) | function filterAsyncRoutesByRoles(routes, roles) {
  function hasPermission (line 105) | function hasPermission(roles, route) {
  function filterAsyncRouterByCodes (line 119) | function filterAsyncRouterByCodes(codesRoutes, codes) {
  function hasCodePermission (line 129) | function hasCodePermission(codes, routeItem) {
  function filterAsyncRouter (line 137) | function filterAsyncRouter({ menuList, roles, codes }) {
  function resetRouter (line 153) | function resetRouter() {
  function resetState (line 164) | function resetState() {
  function freshRouter (line 170) | function freshRouter(data) {

FILE: src/lang/index.ts
  method install (line 16) | install(app) {

FILE: src/plugins/vite-plugin-setup-extend/index.ts
  method transformIndexHtml (line 11) | async transformIndexHtml(html) {
  method transform (line 15) | transform(code, id) {

FILE: src/store/basic.ts
  method remotePromiseArrByReqUrl (line 33) | remotePromiseArrByReqUrl(reqUrl) {
  method clearPromiseArr (line 42) | clearPromiseArr() {
  method setToken (line 47) | setToken(data) {
  method setFilterAsyncRoutes (line 50) | setFilterAsyncRoutes(routes) {
  method setUserInfo (line 56) | setUserInfo({ userInfo, roles, codes }) {
  method resetState (line 67) | resetState() {
  method resetStateAndToLogin (line 82) | resetStateAndToLogin() {
  method setSidebarOpen (line 88) | setSidebarOpen(data) {
  method setToggleSideBar (line 93) | setToggleSideBar() {
  method addCachedView (line 100) | addCachedView(view) {
  method delCachedView (line 107) | delCachedView(view) {
  method addCachedViewDeep (line 114) | addCachedViewDeep(view) {
  method delCacheViewDeep (line 120) | delCacheViewDeep(view) {

FILE: src/store/config.ts
  method setTheme (line 19) | setTheme(data: string) {
  method setSize (line 23) | setSize(data: string) {
  method setLanguage (line 26) | setLanguage(lang: string, title) {

FILE: src/store/tags-view.ts
  method addVisitedView (line 10) | addVisitedView(view) {
  method delVisitedView (line 31) | delVisitedView(view) {
  method delOthersVisitedViews (line 45) | delOthersVisitedViews(view) {
  method delAllVisitedViews (line 55) | delAllVisitedViews() {

FILE: src/utils/axios-req.ts
  function axiosReq (line 113) | function axiosReq(config) {

FILE: src/utils/common-util.ts
  method getWeek (line 2) | getWeek() {
  method mobilePhone (line 8) | mobilePhone(str) {
  method toSplitNumFor (line 15) | toSplitNumFor(num, numToSpace) {
  method bankCardNo (line 19) | bankCardNo(str) {
  method regEmail (line 24) | regEmail(str) {
  method idCardNumber (line 29) | idCardNumber(str) {
  method deleteArrItem (line 39) | deleteArrItem(arr, arrItem) {
  method arrToRepeat (line 47) | arrToRepeat(arr) {
  method deRepeatArr (line 58) | deRepeatArr(seriesArr) {
  method byArrObjDeleteArrObj2 (line 68) | byArrObjDeleteArrObj2(arrObj, arrObj2, objKey) {
  method deleteArrObjByKey (line 87) | deleteArrObjByKey(arrObj, objKey, value) {
  method findArrObjByKey (line 102) | findArrObjByKey(arrObj, objKey, value) {
  method byArrObjFindArrObj2 (line 112) | byArrObjFindArrObj2(arrObj, arrObj2, objKey) {

FILE: src/views/redirect/index.tsx
  method setup (line 3) | setup() {

FILE: ts-out-dir/src/directives/button-codes.js
  function checkPermission (line 2) | function checkPermission(el, { value }) {
  method mounted (line 16) | mounted(el, binding) {
  method componentUpdated (line 19) | componentUpdated(el, binding) {

FILE: ts-out-dir/src/directives/codes-permission.js
  function checkPermission (line 2) | function checkPermission(el, { value }) {
  method mounted (line 16) | mounted(el, binding) {
  method componentUpdated (line 19) | componentUpdated(el, binding) {

FILE: ts-out-dir/src/directives/roles-permission.js
  function checkPermission (line 2) | function checkPermission(el, { value }) {
  method mounted (line 16) | mounted(el, binding) {
  method componentUpdated (line 19) | componentUpdated(el, binding) {

FILE: ts-out-dir/src/hooks/use-common.js
  function cloneDeep (line 24) | function cloneDeep(value) {

FILE: ts-out-dir/src/hooks/use-layout.js
  function isExternal (line 3) | function isExternal(path) {
  function resizeHandler (line 6) | function resizeHandler() {

FILE: ts-out-dir/src/hooks/use-permission.js
  function filterAsyncRoutesByRoles (line 66) | function filterAsyncRoutesByRoles(routes, roles) {
  function hasPermission (line 79) | function hasPermission(roles, route) {
  function filterAsyncRouterByCodes (line 87) | function filterAsyncRouterByCodes(codesRoutes, codes) {
  function hasCodePermission (line 98) | function hasCodePermission(codes, routeItem) {
  function filterAsyncRouter (line 106) | function filterAsyncRouter({ menuList, roles, codes }) {
  function resetRouter (line 123) | function resetRouter() {
  function resetState (line 132) | function resetState() {
  function freshRouter (line 136) | function freshRouter(data) {

FILE: ts-out-dir/src/store/basic.js
  method setToken (line 31) | setToken(data) {
  method setFilterAsyncRoutes (line 34) | setFilterAsyncRoutes(routes) {
  method setUserInfo (line 40) | setUserInfo({ userInfo, roles, codes }) {
  method resetState (line 50) | resetState() {
  method resetStateAndToLogin (line 63) | resetStateAndToLogin() {
  method M_settings (line 69) | M_settings(data) {
  method setSidebarOpen (line 74) | setSidebarOpen(data) {
  method setToggleSideBar (line 79) | setToggleSideBar() {
  method addCachedView (line 84) | addCachedView(view) {
  method delCachedView (line 91) | delCachedView(view) {
  method M_RESET_CACHED_VIEW (line 97) | M_RESET_CACHED_VIEW() {
  method addCachedViewDeep (line 102) | addCachedViewDeep(view) {
  method setCacheViewDeep (line 109) | setCacheViewDeep(view) {
  method M_RESET_CACHED_VIEW_DEEP (line 115) | M_RESET_CACHED_VIEW_DEEP() {
  method A_sidebar_opened (line 120) | A_sidebar_opened(data) {

FILE: ts-out-dir/src/store/tagsView.js
  method addVisitedView (line 10) | addVisitedView(view) {
  method delVisitedView (line 27) | delVisitedView(view) {
  method delOthersVisitedViews (line 40) | delOthersVisitedViews(view) {
  method delAllVisitedViews (line 50) | delAllVisitedViews() {

FILE: ts-out-dir/src/utils/axios-req.js
  function axiosReq (line 46) | function axiosReq(config) {

FILE: ts-out-dir/src/utils/common-util.js
  method getWeek (line 2) | getWeek() {
  method mobilePhone (line 5) | mobilePhone(str) {
  method toSplitNumFor (line 9) | toSplitNumFor(num, numToSpace) {
  method bankCardNo (line 12) | bankCardNo(str) {
  method regEmail (line 16) | regEmail(str) {
  method idCardNumber (line 20) | idCardNumber(str) {
  method deleteArrItem (line 24) | deleteArrItem(arr, arrItem) {
  method arrToRepeat (line 27) | arrToRepeat(arr) {
  method deRepeatArr (line 32) | deRepeatArr(seriesArr) {
  method byArrObjDeleteArrObj2 (line 35) | byArrObjDeleteArrObj2(arrObj, arrObj2, objKey) {
  method deleteArrObjByKey (line 45) | deleteArrObjByKey(arrObj, objKey, value) {
  method findArrObjByKey (line 49) | findArrObjByKey(arrObj, objKey, value) {
  method byArrObjFindArrObj2 (line 52) | byArrObjFindArrObj2(arrObj, arrObj2, objKey) {

FILE: ts-out-dir/src/views/redirect/index.jsx
  method setup (line 3) | setup() {

FILE: typings/basic.d.ts
  type rawConfig (line 14) | interface rawConfig {
  type RouteRawConfig (line 37) | type RouteRawConfig = RouteRecordRaw & rawConfig
  type RouterTypes (line 38) | type RouterTypes = Array<rawProp>
  type SettingsConfig (line 41) | interface SettingsConfig {

FILE: typings/components.d.ts
  type GlobalComponents (line 9) | interface GlobalComponents {

FILE: typings/env.d.ts
  type ImportMetaEnv (line 2) | interface ImportMetaEnv {
  type ImportMeta (line 8) | interface ImportMeta {

FILE: typings/global.d.ts
  type ObjKeys (line 3) | interface ObjKeys {
Condensed preview — 212 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (319K chars).
[
  {
    "path": ".editorconfig",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".eslintignore",
    "chars": 48,
    "preview": "public\nnode_modules\n.history\n.husky\ndist\n*.d.ts\n"
  },
  {
    "path": ".eslintrc.json",
    "chars": 107,
    "preview": "{\n  \"root\": true,\n  \"extends\": [\"./eslintrc/eslint-config.cjs\", \"./eslintrc/.eslintrc-auto-import.json\"]\n}\n"
  },
  {
    "path": ".gitignore",
    "chars": 539,
    "preview": "# compiled output\n/dist\n/dist-ssr\n/node_modules\n\n#lock\npnpm-lock.yaml\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\npnpm-debug.log*\n"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 232,
    "preview": "#!/bin/sh\r\n#. \"$(dirname \"$0\")/_/husky.sh\"\r\n#在项目中我们会使用commit-msg这个git hook来校验我们commit时添加的备注信息是否符合规范。在以前的我们通常是这样配置:\r\n#--n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 106,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\n#推送之前运行eslint检查\nnpm  run lint\n#推送之前运行单元测试检查\n#npm run test:unit\n\n"
  },
  {
    "path": ".npmrc",
    "chars": 115,
    "preview": "shamefully-hoist=true\nstrict-peer-dependencies=false\n\n###aliyun address\nregistry = https://registry.npmmirror.com\n\n"
  },
  {
    "path": ".prettierrc",
    "chars": 221,
    "preview": "{\r\n    \"useTabs\": false,\r\n    \"tabWidth\": 2,\r\n    \"printWidth\": 120,\r\n    \"singleQuote\": true,\r\n    \"trailingComma\": \"no"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 100,
    "preview": "{\n  \"recommendations\": [\"johnsoncodehk.volar\", \"esbenp.prettier-vscode\",\"dbaeumer.vscode-eslint\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 90,
    "preview": "{\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"npm.packageManager\": \"yarn\"\n}\n"
  },
  {
    "path": "README.md",
    "chars": 1734,
    "preview": "# vue3-admin-ts\n\nthe typescript version of  vue3 admin template \n \nsuggestion the Node.js >= v16.20\n\n[Recommended node]("
  },
  {
    "path": "eslintrc/.eslintrc-auto-import.json",
    "chars": 2573,
    "preview": "{\n  \"globals\": {\n    \"EffectScope\": true,\n    \"axiosReq\": true,\n    \"bus\": true,\n    \"buttonCodes\": true,\n    \"casHandle"
  },
  {
    "path": "eslintrc/eslint-config.cjs",
    "chars": 6236,
    "preview": "// eslint-disable-next-line @typescript-eslint/no-var-requires\nconst { defineConfig } = require('eslint-define-config')\n"
  },
  {
    "path": "index.html",
    "chars": 627,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\" class=\"base-theme\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/fa"
  },
  {
    "path": "mock/example.ts",
    "chars": 166,
    "preview": "export default [\n  {\n    url: '/getMapInfo',\n    method: 'get',\n    response: () => {\n      return {\n        code: 200,\n"
  },
  {
    "path": "mock/user.ts",
    "chars": 328,
    "preview": "const user = {\n  url: '/mock/login',\n  method: 'post',\n  response: () => {\n    return {\n      code: 20000,\n      jwtToke"
  },
  {
    "path": "optimize-include.ts",
    "chars": 2439,
    "preview": "// const fs = require('fs')\n// const files = fs.readdirSync(\n//   'D:\\\\github\\\\vue3-admin-ts\\\\node_modules\\\\.pnpm\\\\eleme"
  },
  {
    "path": "package.json",
    "chars": 4591,
    "preview": "{\n    \"name\": \"vue3-admin-ts\",\n    \"version\": \"2.2.0\",\n    \"license\": \"MIT\",\n    \"author\": \"kuanghua(869653722@qq.com)\","
  },
  {
    "path": "src/App.vue",
    "chars": 1357,
    "preview": "<template>\n  <el-config-provider :locale=\"lang[language]\" namespace=\"el\" :size=\"size\">\n    <router-view />\n  </el-config"
  },
  {
    "path": "src/api/user.ts",
    "chars": 618,
    "preview": "//获取用户信息\nimport axiosReq from 'axios'\n// export const userInfoReq = (): Promise<any> => {\n//   return new Promise((resol"
  },
  {
    "path": "src/components/ElSvgIcon.vue",
    "chars": 647,
    "preview": "<template>\n  <el-icon :size=\"size\" :color=\"color\">\n    <component :is=\"ElSvg[name]\" />\n  </el-icon>\n</template>\n\n<script"
  },
  {
    "path": "src/components/TestUnit.vue",
    "chars": 221,
    "preview": "<template>\n  <div>TestUnit.vue</div>\n</template>\n\n<script setup lang=\"ts\">\nconst props = defineProps({\n  msg: {\n    requ"
  },
  {
    "path": "src/components/__tests__/el-svgIcon.test.jsx",
    "chars": 7028,
    "preview": "import { markRaw, nextTick, ref } from 'vue'\nimport { mount } from '@vue/test-utils'\nimport { describe, expect, it, test"
  },
  {
    "path": "src/directives/button-codes.ts",
    "chars": 615,
    "preview": "import { useBasicStore } from '@/store/basic'\n\nfunction checkPermission(el, { value }) {\n  if (value && Array.isArray(va"
  },
  {
    "path": "src/directives/codes-permission.ts",
    "chars": 618,
    "preview": "import { useBasicStore } from '@/store/basic'\nfunction checkPermission(el, { value }) {\n  if (value && Array.isArray(val"
  },
  {
    "path": "src/directives/index.ts",
    "chars": 377,
    "preview": "import buttonCodes from './button-codes'\nimport codesPermission from './codes-permission'\nimport rolesPermission from '."
  },
  {
    "path": "src/directives/lang.ts",
    "chars": 1277,
    "preview": "import { watch } from 'vue'\nimport { storeToRefs } from 'pinia/dist/pinia'\nimport { langTitle } from '@/hooks/use-common"
  },
  {
    "path": "src/directives/roles-permission.ts",
    "chars": 618,
    "preview": "import { useBasicStore } from '@/store/basic'\nfunction checkPermission(el, { value }) {\n  if (value && Array.isArray(val"
  },
  {
    "path": "src/hooks/use-common.ts",
    "chars": 1071,
    "preview": "//复制文本\nimport useClipboard from 'vue-clipboard3'\nimport { ElMessage } from 'element-plus'\n\n// i18n language  match title"
  },
  {
    "path": "src/hooks/use-element.ts",
    "chars": 5057,
    "preview": "import { reactive, ref, toRefs } from 'vue'\nimport { ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element-"
  },
  {
    "path": "src/hooks/use-error-log.ts",
    "chars": 1110,
    "preview": "/*js 错误日志收集*/\nimport { jsErrorCollection } from 'js-error-collection'\nimport axiosReq from 'axios'\nimport pack from '../"
  },
  {
    "path": "src/hooks/use-layout.ts",
    "chars": 1060,
    "preview": "/**\n * 判断是否是外链\n * @param {string} path\n * @returns {Boolean}\n */\nimport { onBeforeMount, onBeforeUnmount, onMounted } fr"
  },
  {
    "path": "src/hooks/use-permission.ts",
    "chars": 5354,
    "preview": "import NProgress from 'nprogress'\nimport type { RouteRawConfig, RouterTypes, rawConfig } from '~/basic'\nimport type { Ro"
  },
  {
    "path": "src/hooks/use-self-router.ts",
    "chars": 758,
    "preview": "import router from '@/router'\nexport const getQueryParam = () => {\n  const route: any = router.currentRoute\n  if (route."
  },
  {
    "path": "src/hooks/use-table.ts",
    "chars": 3018,
    "preview": "import { ref } from 'vue'\nimport momentMini from 'moment-mini'\nimport { elConfirm, elMessage } from './use-element'\nexpo"
  },
  {
    "path": "src/icons/SvgIcon.vue",
    "chars": 738,
    "preview": "<template>\n  <svg :class=\"svgClass\" aria-hidden=\"true\">\n    <use :xlink:href=\"iconName\" :fill=\"color\" />\n  </svg>\n</temp"
  },
  {
    "path": "src/lang/en.ts",
    "chars": 2558,
    "preview": "export default {\n  router: {\n    Dashboard: '',\n    'Setting Switch': '',\n    'Error Log': '',\n    'Error Index': '',\n  "
  },
  {
    "path": "src/lang/index.ts",
    "chars": 453,
    "preview": "import { createI18n } from 'vue-i18n'\nimport en from './en'\nimport zh from './zh'\nimport settings from '@/settings'\ncons"
  },
  {
    "path": "src/lang/zh.ts",
    "chars": 4391,
    "preview": "export default {\n  router: {\n    Dashboard: '首页',\n    LowCodePlatFrom: '低代码平台',\n    RBAC: '用户权限角色',\n    'Setting Switch'"
  },
  {
    "path": "src/layout/app-main/Breadcrumb.vue",
    "chars": 2824,
    "preview": "<template>\n  <el-breadcrumb class=\"app-breadcrumb\" separator=\"/\">\n    <!--  mainNeedAnimation:控制该面包屑是否需要动画  -->\n    <tra"
  },
  {
    "path": "src/layout/app-main/Hamburger.vue",
    "chars": 681,
    "preview": "<template>\n  <div style=\"padding: 0 12px\" @click=\"toggleClick\">\n    <svg-icon icon-class=\"hamburger\" :class=\"{ 'is-activ"
  },
  {
    "path": "src/layout/app-main/Navbar.vue",
    "chars": 3846,
    "preview": "<template>\n  <div class=\"navbar rowBC reset-el-dropdown\">\n    <div class=\"rowSC\">\n      <!--  切换sidebar按钮  -->\n      <ha"
  },
  {
    "path": "src/layout/app-main/TagsView.vue",
    "chars": 8308,
    "preview": "<template>\n  <div id=\"tags-view-container\" class=\"tags-view-container\">\n    <div class=\"tags-view-wrapper\">\n      <route"
  },
  {
    "path": "src/layout/app-main/component/LangSelect.vue",
    "chars": 1232,
    "preview": "<template>\n  <el-dropdown trigger=\"click\" type=\"primary\" @command=\"handleSetLang\">\n    <svg-icon icon-class=\"language\" s"
  },
  {
    "path": "src/layout/app-main/component/ScreenFull.vue",
    "chars": 1141,
    "preview": "<template>\n  <svg-icon\n    :icon-class=\"isFullscreen ? 'exit-fullscreen' : 'fullscreen'\"\n    style=\"width: 17px; height:"
  },
  {
    "path": "src/layout/app-main/component/ScreenLock.vue",
    "chars": 5038,
    "preview": "<template>\n  <svg-icon icon-class=\"lock\" style=\"width: 18px; height: 19px\" class=\"mr-12px\" @click=\"open = true\" />\n  <Te"
  },
  {
    "path": "src/layout/app-main/component/SizeSelect.vue",
    "chars": 1187,
    "preview": "<template>\n  <el-dropdown id=\"size-select\" trigger=\"click\" type=\"primary\" @command=\"handleSetSize\">\n    <svg-icon icon-c"
  },
  {
    "path": "src/layout/app-main/component/ThemeSelect.vue",
    "chars": 1417,
    "preview": "<template>\n  <el-dropdown trigger=\"click\" type=\"primary\" @command=\"handleSetTheme\">\n    <svg-icon icon-class=\"theme-icon"
  },
  {
    "path": "src/layout/app-main/index.vue",
    "chars": 3648,
    "preview": "<template>\n  <div class=\"app-main\" :class=\"{ 'show-tag-view': settings.showTagsView }\">\n    <router-view v-slot=\"{ Compo"
  },
  {
    "path": "src/layout/index.vue",
    "chars": 1655,
    "preview": "<template>\n  <div :class=\"classObj\" class=\"layout-wrapper\">\n    <!--left side-->\n    <Sidebar v-if=\"settings.showLeftMen"
  },
  {
    "path": "src/layout/sidebar/Link.vue",
    "chars": 750,
    "preview": "<template>\n  <component :is=\"type\" v-bind=\"linkProps(to)\">\n    <slot />\n  </component>\n</template>\n\n<script setup lang=\""
  },
  {
    "path": "src/layout/sidebar/Logo.vue",
    "chars": 2051,
    "preview": "<template>\n  <div class=\"sidebar-logo-container\" :class=\"{ collapse: collapse }\">\n    <transition name=\"sidebar-logo-fad"
  },
  {
    "path": "src/layout/sidebar/MenuIcon.vue",
    "chars": 562,
    "preview": "<template>\n  <!-- 如果有 elSvgIcon 显示 elSvgIcon 没有显示 icon-->\n  <el-icon v-if=\"meta?.elSvgIcon\" >\n    <component :is=\"meta.e"
  },
  {
    "path": "src/layout/sidebar/SidebarItem.vue",
    "chars": 2212,
    "preview": "<template>\n  <template v-if=\"!item.hidden\">\n    <template v-if=\"showSidebarItem(item.children, item)\">\n      <Link v-if="
  },
  {
    "path": "src/layout/sidebar/index.vue",
    "chars": 1313,
    "preview": "<template>\n  <div id=\"Sidebar\" class=\"reset-menu-style\">\n    <!--logo-->\n    <Logo v-if=\"settings.sidebarLogo\" :collapse"
  },
  {
    "path": "src/lib/el-svg-icon.ts",
    "chars": 275,
    "preview": "import * as components from '@element-plus/icons-vue'\n\nexport default {\n    install: (app) => {\n        for (const key i"
  },
  {
    "path": "src/lib/element-plus.ts",
    "chars": 271,
    "preview": "import * as AllComponent from 'element-plus'\n//element-plus中按需引入会引起首次加载过慢\nconst elementPlusComponentNameArr = ['ElButton"
  },
  {
    "path": "src/main.ts",
    "chars": 1002,
    "preview": "import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport piniaPluginPersistedstate from 'pinia-plugin-"
  },
  {
    "path": "src/mock-prod-server.ts",
    "chars": 536,
    "preview": "// vite-plugin-mock v3.0.1 版本 生产mock有问题\n// import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServe"
  },
  {
    "path": "src/permission.ts",
    "chars": 965,
    "preview": "import router from '@/router'\nimport {progressClose, progressStart } from '@/hooks/use-permission'\nimport { useBasicStor"
  },
  {
    "path": "src/plugins/vite-plugin-setup-extend/index.ts",
    "chars": 993,
    "preview": "import { parse } from '@vue/compiler-sfc'\nimport { render } from 'ejs'\nimport type { Plugin } from 'vite'\nexport default"
  },
  {
    "path": "src/router/index.ts",
    "chars": 3676,
    "preview": "import { createRouter, createWebHashHistory } from 'vue-router'\nimport basicDemo from './modules/basic-demo'\nimport type"
  },
  {
    "path": "src/router/modules/basic-demo.ts",
    "chars": 2776,
    "preview": "import Layout from '@/layout/index.vue'\nconst BasicDemo = {\n  path: '/basic-demo',\n  component: Layout,\n  meta: { title:"
  },
  {
    "path": "src/settings.ts",
    "chars": 2607,
    "preview": "import packageJson from '../package.json'\nimport type { SettingsConfig } from '~/basic'\nexport const settings: SettingsC"
  },
  {
    "path": "src/store/basic.ts",
    "chars": 3341,
    "preview": "import { nextTick } from 'vue'\nimport { defineStore } from 'pinia'\nimport type { RouterTypes } from '~/basic'\nimport def"
  },
  {
    "path": "src/store/config.ts",
    "chars": 851,
    "preview": "import { defineStore } from 'pinia'\nimport { langTitle } from '@/hooks/use-common'\nimport settings from '@/settings'\nimp"
  },
  {
    "path": "src/store/tags-view.ts",
    "chars": 1909,
    "preview": "import { defineStore } from 'pinia'\nimport setting from '@/settings'\nexport const useTagsViewStore = defineStore('tagsVi"
  },
  {
    "path": "src/styles/index.scss",
    "chars": 1002,
    "preview": "//scss 语法糖包含常用的布局方式 flex column\n@import './scss-suger.scss';\n//重置 element-plus 样式\n@import 'reset-elemenet-plus.scss';\n//"
  },
  {
    "path": "src/styles/init-loading.css",
    "chars": 219,
    "preview": "/*开屏loading样式设置*/\n.loader-wrapper{\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: "
  },
  {
    "path": "src/styles/project-style.scss",
    "chars": 720,
    "preview": ".project-page-style {\n  background: #fff;\n  padding: 20px;\n  padding-top: 0;\n}\n\n.query-page-style {\n  background: #fff;\n"
  },
  {
    "path": "src/styles/reset-elemenet-plus.scss",
    "chars": 1184,
    "preview": "//leave the padding of  el-scroll sidebar\n#Sidebar{\n  .el-scrollbar__view {\n    padding-bottom: 80px;\n  }\n  .el-scrollba"
  },
  {
    "path": "src/styles/scss-suger.scss",
    "chars": 4533,
    "preview": "/*脱落文档流定位*/\n.center-50 {\n  //居中定位\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  z"
  },
  {
    "path": "src/styles/transition.scss",
    "chars": 856,
    "preview": "// vue global transition css define\n/* sidebar-logo-fade */\n.sidebar-logo-fade-enter-active {\n  transition: opacity var("
  },
  {
    "path": "src/theme/base/custom/ct-css-vars.scss",
    "chars": 2064,
    "preview": "html.base-theme {\n  /*element-plus section */\n  --el-menu-active-color: #409eff;\n  --el-menu-text-color: #bfcbd9;\n  --el"
  },
  {
    "path": "src/theme/base/element-plus/button.scss",
    "chars": 3361,
    "preview": "html.base-theme {\n  .at-button-low {\n    --el-button-text-color: #262626;\n    --el-button-bg-color: #ffffff;\n    --el-bu"
  },
  {
    "path": "src/theme/base/element-plus/checkbox.scss",
    "chars": 1149,
    "preview": "html.china-red {\n  .el-checkbox {\n    --el-checkbox-font-size: 14px;\n    --el-checkbox-font-weight: var(--el-font-weight"
  },
  {
    "path": "src/theme/base/element-plus/css-vars.scss",
    "chars": 424,
    "preview": "@use 'sass:map';\n\n@use './var' as *;\n@use '../../mixins/var'  as *;\n@use '../../mixins/mixins'  as *;\n\nhtml.china-red {\n"
  },
  {
    "path": "src/theme/base/element-plus/form.scss",
    "chars": 814,
    "preview": "html.china-red {\n  //date\n  .el-date-range-picker {\n    --el-datepicker-text-color: var(--el-text-color-regular);\n    --"
  },
  {
    "path": "src/theme/base/element-plus/pagination.scss",
    "chars": 1097,
    "preview": "html.china-red {\n  .el-pagination {\n    --el-text-color-regular: #8c8c8c;\n    --el-pagination-font-size: 14px;\n    --el-"
  },
  {
    "path": "src/theme/base/element-plus/redio.scss",
    "chars": 607,
    "preview": "html.china-red {\n  .el-radio {\n    --el-radio-font-size: var(--el-font-size-base);\n    --el-radio-text-color: #262626;\n "
  },
  {
    "path": "src/theme/base/element-plus/table.scss",
    "chars": 792,
    "preview": "html.china-red {\n  .el-table {\n    --el-table-border-color: #f0f0f0;\n    --el-table-border: 1px solid #f0f0f0;\n    --el-"
  },
  {
    "path": "src/theme/base/element-plus/var.scss",
    "chars": 1532,
    "preview": "/* Element Chalk Variables */\n@use 'sass:math';\n@use 'sass:map';\n@use '../../mixins/function.scss' as *;\n\n// types\n$type"
  },
  {
    "path": "src/theme/base/index.scss",
    "chars": 309,
    "preview": "/*china-red*/\n//element-plus\n@use \"./element-plus/css-vars\";\n@use \"./element-plus/var\";\n@use \"./element-plus/button\";\n@u"
  },
  {
    "path": "src/theme/china-red/custom/ct-css-vars.scss",
    "chars": 2915,
    "preview": "html.china-red {\n  --el-menu-active-color: var(--el-color-primary);\n  --el-menu-text-color: var(--el-text-color-primary)"
  },
  {
    "path": "src/theme/china-red/element-plus/button.scss",
    "chars": 3387,
    "preview": "html.china-red {\n  color-scheme: china-red;\n  .at-button-low {\n    --el-button-text-color: #262626;\n    --el-button-bg-c"
  },
  {
    "path": "src/theme/china-red/element-plus/checkbox.scss",
    "chars": 1149,
    "preview": "html.china-red {\n  .el-checkbox {\n    --el-checkbox-font-size: 14px;\n    --el-checkbox-font-weight: var(--el-font-weight"
  },
  {
    "path": "src/theme/china-red/element-plus/css-vars.scss",
    "chars": 424,
    "preview": "@use 'sass:map';\n\n@use './var' as *;\n@use '../../mixins/var'  as *;\n@use '../../mixins/mixins'  as *;\n\nhtml.china-red {\n"
  },
  {
    "path": "src/theme/china-red/element-plus/form.scss",
    "chars": 814,
    "preview": "html.china-red {\n  //date\n  .el-date-range-picker {\n    --el-datepicker-text-color: var(--el-text-color-regular);\n    --"
  },
  {
    "path": "src/theme/china-red/element-plus/pagination.scss",
    "chars": 1097,
    "preview": "html.china-red {\n  .el-pagination {\n    --el-text-color-regular: #8c8c8c;\n    --el-pagination-font-size: 14px;\n    --el-"
  },
  {
    "path": "src/theme/china-red/element-plus/redio.scss",
    "chars": 607,
    "preview": "html.china-red {\n  .el-radio {\n    --el-radio-font-size: var(--el-font-size-base);\n    --el-radio-text-color: #262626;\n "
  },
  {
    "path": "src/theme/china-red/element-plus/table.scss",
    "chars": 792,
    "preview": "html.china-red {\n  .el-table {\n    --el-table-border-color: #f0f0f0;\n    --el-table-border: 1px solid #f0f0f0;\n    --el-"
  },
  {
    "path": "src/theme/china-red/element-plus/var.scss",
    "chars": 1532,
    "preview": "/* Element Chalk Variables */\n@use 'sass:math';\n@use 'sass:map';\n@use '../../mixins/function.scss' as *;\n\n// types\n$type"
  },
  {
    "path": "src/theme/china-red/index.scss",
    "chars": 309,
    "preview": "/*china-red*/\n//element-plus\n@use \"./element-plus/css-vars\";\n@use \"./element-plus/var\";\n@use \"./element-plus/button\";\n@u"
  },
  {
    "path": "src/theme/dark/custom/ct-css-vars.scss",
    "chars": 2105,
    "preview": "html.dark {\n  /*element-plus section */\n  --el-menu-active-color: #409eff;\n  --el-menu-text-color: #bfcbd9;\n  --el-menu-"
  },
  {
    "path": "src/theme/dark/element-plus/button.scss",
    "chars": 3387,
    "preview": "html.china-red {\n  color-scheme: china-red;\n  .at-button-low {\n    --el-button-text-color: #262626;\n    --el-button-bg-c"
  },
  {
    "path": "src/theme/dark/element-plus/checkbox.scss",
    "chars": 1149,
    "preview": "html.china-red {\n  .el-checkbox {\n    --el-checkbox-font-size: 14px;\n    --el-checkbox-font-weight: var(--el-font-weight"
  },
  {
    "path": "src/theme/dark/element-plus/css-vars.css",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/theme/dark/element-plus/css-vars.scss",
    "chars": 424,
    "preview": "@use 'sass:map';\n\n@use './var' as *;\n@use '../../mixins/var'  as *;\n@use '../../mixins/mixins'  as *;\n\nhtml.china-red {\n"
  },
  {
    "path": "src/theme/dark/element-plus/form.scss",
    "chars": 814,
    "preview": "html.china-red {\n  //date\n  .el-date-range-picker {\n    --el-datepicker-text-color: var(--el-text-color-regular);\n    --"
  },
  {
    "path": "src/theme/dark/element-plus/pagination.scss",
    "chars": 1097,
    "preview": "html.china-red {\n  .el-pagination {\n    --el-text-color-regular: #8c8c8c;\n    --el-pagination-font-size: 14px;\n    --el-"
  },
  {
    "path": "src/theme/dark/element-plus/redio.scss",
    "chars": 607,
    "preview": "html.china-red {\n  .el-radio {\n    --el-radio-font-size: var(--el-font-size-base);\n    --el-radio-text-color: #262626;\n "
  },
  {
    "path": "src/theme/dark/element-plus/table.scss",
    "chars": 792,
    "preview": "html.china-red {\n  .el-table {\n    --el-table-border-color: #f0f0f0;\n    --el-table-border: 1px solid #f0f0f0;\n    --el-"
  },
  {
    "path": "src/theme/dark/element-plus/var.scss",
    "chars": 1532,
    "preview": "/* Element Chalk Variables */\n@use 'sass:math';\n@use 'sass:map';\n@use '../../mixins/function.scss' as *;\n\n// types\n$type"
  },
  {
    "path": "src/theme/dark/index.scss",
    "chars": 325,
    "preview": "/*china-red*/\n//element-plus\n//@use \"./element-plus/css-vars\";\n//@use \"./element-plus/var\";\n//@use \"./element-plus/butto"
  },
  {
    "path": "src/theme/index.css",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/theme/index.scss",
    "chars": 275,
    "preview": "// we can add this to custom namespace, default is 'el'\n//@forward \"element-plus/theme-chalk/src/mixins/config.scss\" wit"
  },
  {
    "path": "src/theme/lighting/custom/ct-css-vars.scss",
    "chars": 2085,
    "preview": "html.lighting-theme {\n  /*element-plus section */\n  //--el-menu-active-color: #409eff;\n  //--el-menu-text-color: #bfcbd9"
  },
  {
    "path": "src/theme/lighting/element-plus/button.scss",
    "chars": 3387,
    "preview": "html.china-red {\n  color-scheme: china-red;\n  .at-button-low {\n    --el-button-text-color: #262626;\n    --el-button-bg-c"
  },
  {
    "path": "src/theme/lighting/element-plus/checkbox.scss",
    "chars": 1149,
    "preview": "html.china-red {\n  .el-checkbox {\n    --el-checkbox-font-size: 14px;\n    --el-checkbox-font-weight: var(--el-font-weight"
  },
  {
    "path": "src/theme/lighting/element-plus/css-vars.css",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/theme/lighting/element-plus/css-vars.scss",
    "chars": 423,
    "preview": "@use 'sass:map';\n\n@use './var' as *;\n@use '../../mixins/var'  as *;\n@use '../../mixins/mixins'  as *;\n\nhtml.china-red {\n"
  },
  {
    "path": "src/theme/lighting/element-plus/form.scss",
    "chars": 814,
    "preview": "html.china-red {\n  //date\n  .el-date-range-picker {\n    --el-datepicker-text-color: var(--el-text-color-regular);\n    --"
  },
  {
    "path": "src/theme/lighting/element-plus/pagination.scss",
    "chars": 1097,
    "preview": "html.china-red {\n  .el-pagination {\n    --el-text-color-regular: #8c8c8c;\n    --el-pagination-font-size: 14px;\n    --el-"
  },
  {
    "path": "src/theme/lighting/element-plus/redio.scss",
    "chars": 607,
    "preview": "html.china-red {\n  .el-radio {\n    --el-radio-font-size: var(--el-font-size-base);\n    --el-radio-text-color: #262626;\n "
  },
  {
    "path": "src/theme/lighting/element-plus/table.scss",
    "chars": 792,
    "preview": "html.china-red {\n  .el-table {\n    --el-table-border-color: #f0f0f0;\n    --el-table-border: 1px solid #f0f0f0;\n    --el-"
  },
  {
    "path": "src/theme/lighting/element-plus/var.scss",
    "chars": 1532,
    "preview": "/* Element Chalk Variables */\n@use 'sass:math';\n@use 'sass:map';\n@use '../../mixins/function.scss' as *;\n\n// types\n$type"
  },
  {
    "path": "src/theme/lighting/index.scss",
    "chars": 325,
    "preview": "/*china-red*/\n//element-plus\n//@use \"./element-plus/css-vars\";\n//@use \"./element-plus/var\";\n//@use \"./element-plus/butto"
  },
  {
    "path": "src/theme/mixins/_var.scss",
    "chars": 1641,
    "preview": "/*var mixin*/\n@use 'sass:map';\n\n@use 'config';\n@use 'function' as *;\n\n// set css var value, because we need translate va"
  },
  {
    "path": "src/theme/mixins/config.scss",
    "chars": 162,
    "preview": "$namespace: 'el' !default;\n$common-separator: '-' !default;\n$element-separator: '__' !default;\n$modifier-separator: '--'"
  },
  {
    "path": "src/theme/mixins/function.scss",
    "chars": 1409,
    "preview": "@use 'config';\n\n// getCssVarName('button', 'text-color') => '--el-button-text-color'\n@function getCssVarName($args...) {"
  },
  {
    "path": "src/theme/mixins/mixins.scss",
    "chars": 1033,
    "preview": "//input function\n@use 'function' as *;\n@forward 'function';\n\n@forward 'config';\n@use 'config' as *;\n// el-button{}\n@mixi"
  },
  {
    "path": "src/theme/utils/change-theme.ts",
    "chars": 111,
    "preview": "export const toggleHtmlClass = (className) => {\n  document.querySelectorAll('html')[0].className = className\n}\n"
  },
  {
    "path": "src/theme/utils/index.ts",
    "chars": 31,
    "preview": "export * from './change-theme'\n"
  },
  {
    "path": "src/utils/axios-req.ts",
    "chars": 2886,
    "preview": "import axios from 'axios'\nimport { ElLoading, ElMessage, ElMessageBox } from 'element-plus'\nimport { useBasicStore } fro"
  },
  {
    "path": "src/utils/bus.ts",
    "chars": 57,
    "preview": "//bus even\nimport mitt from 'mitt'\nexport default mitt()\n"
  },
  {
    "path": "src/utils/common-util.ts",
    "chars": 2869,
    "preview": "export default {\n  getWeek() {\n    return `星期${'日一二三四五六'.charAt(new Date().getDay())}`\n    // this.showDate=this.$moment"
  },
  {
    "path": "src/views/basic-demo/hook/index.vue",
    "chars": 356,
    "preview": "<template>\n  <div>\n    <div class=\"font-bold mb-20px\">引入use-common.ts中的copyValueToClipboard</div>\n    <el-button @click="
  },
  {
    "path": "src/views/basic-demo/keep-alive/index.vue",
    "chars": 1230,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div class=\"font-bold mb-10px\">Second Page KeepAlive Demo</div>\n\n    <el-form re"
  },
  {
    "path": "src/views/basic-demo/keep-alive/second-child.vue",
    "chars": 1084,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div class=\"font-bold mb-20px\">second-level.vue</div>\n    <el-form ref=\"refsearc"
  },
  {
    "path": "src/views/basic-demo/keep-alive/second-keep-alive.vue",
    "chars": 1231,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div class=\"font-bold mb-10px\">Second Page KeepAlive Demo</div>\n\n    <el-form re"
  },
  {
    "path": "src/views/basic-demo/keep-alive/tab-keep-alive.vue",
    "chars": 476,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div class=\"font-bold mb-10px\">Tab KeepAlive Demo</div>\n    <el-input v-model=\"s"
  },
  {
    "path": "src/views/basic-demo/keep-alive/third-child.vue",
    "chars": 934,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <h2 class=\"font-bold mb-10px\">third-level.vue</h2>\n    <el-form ref=\"refsearchFo"
  },
  {
    "path": "src/views/basic-demo/mock/index.vue",
    "chars": 375,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div class=\"font-bold mb-20px\">mock使用示例</div>\n    <el-button type=\"primary\" @cli"
  },
  {
    "path": "src/views/basic-demo/parent-children/Children.vue",
    "chars": 1763,
    "preview": "<template>\n  <div>\n    <div class=\"font-bold mb-10px mt-20px\">call father method</div>\n    <el-button @click=\"emitFather"
  },
  {
    "path": "src/views/basic-demo/parent-children/SubChildren.vue",
    "chars": 919,
    "preview": "<template>\n  <div class=\"font-bold mt-20px mb-10px\">provide and inject using</div>\n  <div>{{ title }}</div>\n\n  <div clas"
  },
  {
    "path": "src/views/basic-demo/parent-children/index.vue",
    "chars": 1350,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div class=\"font-bold mb-10px\">call child method</div>\n    <el-button @click=\"ch"
  },
  {
    "path": "src/views/basic-demo/pinia/index.vue",
    "chars": 399,
    "preview": "<template>\n  <div>\n    <div class=\"font-bold mb-20px\">由于使用了 AutoImport 插件 可以直接引入pinia里的api</div>\n    <el-button @click=\""
  },
  {
    "path": "src/views/basic-demo/svg-icon/index.vue",
    "chars": 394,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div class=\"font-bold mb-20px\">svg-icon使用示例</div>\n    <svg-icon icon-class=\"side"
  },
  {
    "path": "src/views/basic-demo/vue3-template/Vue3Template.vue",
    "chars": 643,
    "preview": "<template>\n  <div>vue3推荐模板可以集成在你们的vscode或webstorm中,有助于快速开发</div>\n</template>\n\n<script setup>\n// 获取store和router\n\nconst pr"
  },
  {
    "path": "src/views/basic-demo/worker/index.vue",
    "chars": 1081,
    "preview": "<template>\n  <div>\n    <div>the recommend using way of worker</div>\n    <div>计算结果:{{ showPageRef }}</div>\n    <el-icon v"
  },
  {
    "path": "src/views/dashboard/index.vue",
    "chars": 2217,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <div v-lang class=\"mt-10px mb-10px font-bold\">switch theme</div>\n    <el-button "
  },
  {
    "path": "src/views/error-page/401.vue",
    "chars": 2180,
    "preview": "<template>\n  <div class=\"errPage-container\">\n    <el-button icon=\"el-icon-arrow-left\" class=\"pan-back-btn\" @click=\"back\""
  },
  {
    "path": "src/views/error-page/404.vue",
    "chars": 5228,
    "preview": "<template>\n  <div class=\"wscn-http404-container\">\n    <div class=\"wscn-http404\">\n      <div class=\"pic-404\">\n        <im"
  },
  {
    "path": "src/views/login/index.vue",
    "chars": 4908,
    "preview": "<template>\n  <div class=\"login-container columnCC\">\n    <el-form ref=\"refLoginForm\" class=\"login-form\" :model=\"subForm\" "
  },
  {
    "path": "src/views/nested/menu1/index.vue",
    "chars": 206,
    "preview": "<template>\n  <div>\n    <router-view v-slot=\"{ Component }\">\n      <el-alert :closable=\"false\" title=\"menu 1\">\n        <c"
  },
  {
    "path": "src/views/nested/menu1/menu1-1/index.vue",
    "chars": 276,
    "preview": "<template>\n  <div>\n    <router-view v-slot=\"{ Component }\">\n      <div style=\"padding: 15px\">\n        <el-alert :closabl"
  },
  {
    "path": "src/views/nested/menu1/menu1-2/index.vue",
    "chars": 247,
    "preview": "<template>\n  <router-view v-slot=\"{ Component }\">\n    <div style=\"padding: 15px\">\n      <el-alert :closable=\"false\" titl"
  },
  {
    "path": "src/views/nested/menu1/menu1-2/menu1-2-1/index.vue",
    "chars": 131,
    "preview": "<template>\n  <div style=\"padding: 30px\">\n    <el-alert :closable=\"false\" title=\"menu 1-2-1\" type=\"warning\" />\n  </div>\n<"
  },
  {
    "path": "src/views/nested/menu1/menu1-2/menu1-2-2/index.vue",
    "chars": 131,
    "preview": "<template>\n  <div style=\"padding: 30px\">\n    <el-alert :closable=\"false\" title=\"menu 1-2-2\" type=\"warning\" />\n  </div>\n<"
  },
  {
    "path": "src/views/nested/menu1/menu1-3/index.vue",
    "chars": 107,
    "preview": "<template>\n  <div>\n    <el-alert :closable=\"false\" title=\"menu 1-3\" type=\"success\" />\n  </div>\n</template>\n"
  },
  {
    "path": "src/views/nested/menu2/index.vue",
    "chars": 146,
    "preview": "<template>\n  <div style=\"padding: 15px\">\n    <el-alert :closable=\"false\" title=\"menu 2\" />\n  </div>\n</template>\n<script "
  },
  {
    "path": "src/views/redirect/index.tsx",
    "chars": 331,
    "preview": "import { defineComponent } from 'vue'\nexport default defineComponent({\n  setup() {\n    const route = useRoute()\n    cons"
  },
  {
    "path": "src/views/setting-switch/SettingSwitch.vue",
    "chars": 1563,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <h3 class=\"mb-20px\">props operate demo of settings.js</h3>\n    <div class=\"rowSS"
  },
  {
    "path": "src/views/setting-switch/index.vue",
    "chars": 1563,
    "preview": "<template>\n  <div class=\"scroll-y\">\n    <h3 class=\"mb-20px\">props operate demo of settings.js</h3>\n    <div class=\"rowSS"
  },
  {
    "path": "ts-out-dir/package.json",
    "chars": 4137,
    "preview": "{\n    \"name\": \"vue3-admin-ts\",\n    \"version\": \"2.0.0-rc2\",\n    \"license\": \"MIT\",\n    \"author\": \"kuanghua\",\n    \"packageM"
  },
  {
    "path": "ts-out-dir/src/api/user.d.ts",
    "chars": 213,
    "preview": "export declare const userInfoReq: () => Promise<any>;\nexport declare const loginReq: (subForm: any) => import(\"axios\").A"
  },
  {
    "path": "ts-out-dir/src/api/user.js",
    "chars": 668,
    "preview": "import axiosReq from '@/utils/axios-req';\nexport const userInfoReq = () => {\n    return new Promise((resolve) => {\n     "
  },
  {
    "path": "ts-out-dir/src/directives/button-codes.d.ts",
    "chars": 147,
    "preview": "declare const _default: {\n    mounted(el: any, binding: any): void;\n    componentUpdated(el: any, binding: any): void;\n}"
  },
  {
    "path": "ts-out-dir/src/directives/button-codes.js",
    "chars": 694,
    "preview": "import { useBasicStore } from '@/store/basic';\nfunction checkPermission(el, { value }) {\n    if (value && Array.isArray("
  },
  {
    "path": "ts-out-dir/src/directives/codes-permission.d.ts",
    "chars": 147,
    "preview": "declare const _default: {\n    mounted(el: any, binding: any): void;\n    componentUpdated(el: any, binding: any): void;\n}"
  },
  {
    "path": "ts-out-dir/src/directives/codes-permission.js",
    "chars": 698,
    "preview": "import { useBasicStore } from '@/store/basic';\nfunction checkPermission(el, { value }) {\n    if (value && Array.isArray("
  },
  {
    "path": "ts-out-dir/src/directives/index.d.ts",
    "chars": 42,
    "preview": "export default function (app: any): void;\n"
  },
  {
    "path": "ts-out-dir/src/directives/index.js",
    "chars": 333,
    "preview": "import buttonCodes from './button-codes';\nimport codesPermission from './codes-permission';\nimport rolesPermission from "
  },
  {
    "path": "ts-out-dir/src/directives/roles-permission.d.ts",
    "chars": 147,
    "preview": "declare const _default: {\n    mounted(el: any, binding: any): void;\n    componentUpdated(el: any, binding: any): void;\n}"
  },
  {
    "path": "ts-out-dir/src/directives/roles-permission.js",
    "chars": 698,
    "preview": "import { useBasicStore } from '@/store/basic';\nfunction checkPermission(el, { value }) {\n    if (value && Array.isArray("
  },
  {
    "path": "ts-out-dir/src/hooks/use-common.d.ts",
    "chars": 447,
    "preview": "export declare const sleepTimeout: (time: number) => Promise<unknown>;\nexport declare const useCommon: () => {\n    total"
  },
  {
    "path": "ts-out-dir/src/hooks/use-common.js",
    "chars": 821,
    "preview": "import { reactive, toRefs } from 'vue';\nimport useClipboard from 'vue-clipboard3';\nimport { ElMessage } from 'element-pl"
  },
  {
    "path": "ts-out-dir/src/hooks/use-element.d.ts",
    "chars": 2670,
    "preview": "import type { EpPropMergeType } from 'element-plus/es/utils';\nexport declare const useElement: () => {\n    tableData: im"
  },
  {
    "path": "ts-out-dir/src/hooks/use-element.js",
    "chars": 4801,
    "preview": "import { reactive, ref, toRefs } from 'vue';\nimport { ElLoading, ElMessage, ElMessageBox, ElNotification } from 'element"
  },
  {
    "path": "ts-out-dir/src/hooks/use-error-log.d.ts",
    "chars": 46,
    "preview": "export declare const useErrorLog: () => void;\n"
  },
  {
    "path": "ts-out-dir/src/hooks/use-error-log.js",
    "chars": 916,
    "preview": "import { jsErrorCollection } from 'js-error-collection';\nimport pack from '../../package.json';\nimport settings from '@/"
  },
  {
    "path": "ts-out-dir/src/hooks/use-layout.d.ts",
    "chars": 103,
    "preview": "export declare function isExternal(path: any): boolean;\nexport declare function resizeHandler(): void;\n"
  },
  {
    "path": "ts-out-dir/src/hooks/use-layout.js",
    "chars": 1084,
    "preview": "import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue';\nimport { useBasicStore } from '@/store/basic';\nexport f"
  },
  {
    "path": "ts-out-dir/src/hooks/use-permission.d.ts",
    "chars": 713,
    "preview": "import type { RouterTypes } from '~/basic';\nimport 'nprogress/nprogress.css';\nexport declare const filterAsyncRoutesByMe"
  },
  {
    "path": "ts-out-dir/src/hooks/use-permission.js",
    "chars": 4732,
    "preview": "import NProgress from 'nprogress';\nimport Layout from '@/layout/index.vue';\nimport router, { asyncRoutes, constantRoutes"
  },
  {
    "path": "ts-out-dir/src/hooks/use-self-router.d.ts",
    "chars": 229,
    "preview": "export declare const getQueryParam: () => any;\nexport declare const routerPush: (name: any, params: any) => void;\nexport"
  },
  {
    "path": "ts-out-dir/src/hooks/use-self-router.js",
    "chars": 786,
    "preview": "import router from '@/router';\nexport const getQueryParam = () => {\n    const route = router.currentRoute;\n    if (route"
  },
  {
    "path": "ts-out-dir/src/hooks/use-table.d.ts",
    "chars": 681,
    "preview": "export declare const useTable: (searchForm: any, selectPageReq: any) => {\n    pageNum: import(\"vue\").Ref<number>;\n    pa"
  },
  {
    "path": "ts-out-dir/src/hooks/use-table.js",
    "chars": 3322,
    "preview": "import { ref } from 'vue';\nimport momentMini from 'moment-mini';\nimport { elConfirm, elMessage } from './use-element';\ne"
  },
  {
    "path": "ts-out-dir/src/lib/element-plus.d.ts",
    "chars": 42,
    "preview": "export default function (app: any): void;\n"
  },
  {
    "path": "ts-out-dir/src/lib/element-plus.js",
    "chars": 254,
    "preview": "import * as AllComponent from 'element-plus';\nconst elementPlusComponentNameArr = ['ElButton'];\nexport default function "
  },
  {
    "path": "ts-out-dir/src/main.d.ts",
    "chars": 175,
    "preview": "import '@/styles/index.scss';\nimport 'virtual:svg-icons-register';\nimport './permission';\nimport './theme/index.scss';\ni"
  },
  {
    "path": "ts-out-dir/src/main.js",
    "chars": 710,
    "preview": "import { createApp } from 'vue';\nimport App from './App.vue';\nconst app = createApp(App);\nimport router from './router';"
  },
  {
    "path": "ts-out-dir/src/permission.d.ts",
    "chars": 11,
    "preview": "export {};\n"
  },
  {
    "path": "ts-out-dir/src/permission.js",
    "chars": 1332,
    "preview": "import router from '@/router';\nimport { filterAsyncRouter, progressClose, progressStart } from '@/hooks/use-permission';"
  },
  {
    "path": "ts-out-dir/src/router/index.d.ts",
    "chars": 265,
    "preview": "import type { RouterTypes } from '~/basic';\nexport declare const constantRoutes: RouterTypes;\nexport declare const roleC"
  },
  {
    "path": "ts-out-dir/src/router/index.js",
    "chars": 6438,
    "preview": "import { createRouter, createWebHashHistory } from 'vue-router';\nimport Layout from '@/layout/index.vue';\nexport const c"
  },
  {
    "path": "ts-out-dir/src/settings.d.ts",
    "chars": 112,
    "preview": "import type { SettingsConfig } from '~/basic';\ndeclare const settings: SettingsConfig;\nexport default settings;\n"
  },
  {
    "path": "ts-out-dir/src/settings.js",
    "chars": 510,
    "preview": "const settings = {\n    title: 'Vue3 Admin Template',\n    sidebarLogo: true,\n    showNavbarTitle: false,\n    ShowDropDown"
  },
  {
    "path": "ts-out-dir/src/store/basic.d.ts",
    "chars": 1185,
    "preview": "import type { RouterTypes } from '~/basic';\nexport declare const useBasicStore: import(\"pinia\").StoreDefinition<\"basic\","
  },
  {
    "path": "ts-out-dir/src/store/basic.js",
    "chars": 3834,
    "preview": "import { nextTick } from 'vue';\nimport { defineStore } from 'pinia';\nimport defaultSettings from '@/settings';\nimport ro"
  },
  {
    "path": "ts-out-dir/src/store/tagsView.d.ts",
    "chars": 311,
    "preview": "export declare const useTagsViewStore: import(\"pinia\").StoreDefinition<\"tagsView\", {\n    visitedViews: never[];\n}, {}, {"
  },
  {
    "path": "ts-out-dir/src/store/tagsView.js",
    "chars": 2116,
    "preview": "import { defineStore } from 'pinia';\nimport setting from '@/settings';\nexport const useTagsViewStore = defineStore('tags"
  },
  {
    "path": "ts-out-dir/src/utils/axios-req.d.ts",
    "chars": 82,
    "preview": "export default function axiosReq(config: any): import(\"axios\").AxiosPromise<any>;\n"
  },
  {
    "path": "ts-out-dir/src/utils/axios-req.js",
    "chars": 1520,
    "preview": "import axios from 'axios';\nimport { ElMessage, ElMessageBox } from 'element-plus';\nimport { useBasicStore } from '@/stor"
  },
  {
    "path": "ts-out-dir/src/utils/bus.d.ts",
    "chars": 116,
    "preview": "declare const _default: import(\"mitt\").Emitter<Record<import(\"mitt\").EventType, unknown>>;\nexport default _default;\n"
  },
  {
    "path": "ts-out-dir/src/utils/bus.js",
    "chars": 48,
    "preview": "import mitt from 'mitt';\nexport default mitt();\n"
  },
  {
    "path": "ts-out-dir/src/utils/common-util.d.ts",
    "chars": 668,
    "preview": "declare const _default: {\n    getWeek(): string;\n    mobilePhone(str: any): boolean;\n    toSplitNumFor(num: any, numToSp"
  },
  {
    "path": "ts-out-dir/src/utils/common-util.js",
    "chars": 1969,
    "preview": "export default {\n    getWeek() {\n        return `星期${'日一二三四五六'.charAt(new Date().getDay())}`;\n    },\n    mobilePhone(str"
  },
  {
    "path": "ts-out-dir/src/views/redirect/index.d.ts",
    "chars": 352,
    "preview": "declare const _default: import(\"vue\").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import(\"vue\").ComponentOptionsM"
  }
]

// ... and 12 more files (download for full content)

About this extraction

This page contains the full source code of the jzfai/vue3-admin-ts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 212 files (277.3 KB), approximately 85.3k tokens, and a symbol index with 123 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!