This commit is contained in:
4
.browserslistrc
Normal file
4
.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
104
.env
Normal file
104
.env
Normal file
@@ -0,0 +1,104 @@
|
||||
# Vab Admin 系列产品受国家计算机软件著作权保护(证书号:软著登字第 7051316 号)。
|
||||
# 关于举报盗版侵权:请发送举报材料至我司客服邮箱1204505056@qq.com,一经查实,官司所得收入20%归举报人所有,80%归律师事务所所有。
|
||||
# Vue Admin系列产品购买地址://vuejs-core.cn/authorization
|
||||
# 1.购买者可将授权后的产品用于任意「符合国家法律法规」的应用平台,禁止用于黄赌毒等危害国家安全与稳定的网站。
|
||||
# 2.购买主体购买后可用于开发商业项目,不限制域名和项目数量,购买主体不可将源码分享第三方,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
# 3.购买者务必尊重知识产权,严格保证不恶意传播产品源码、不得直接对授权的产品本身进行二次转售或倒卖、开源、不得对授权的产品进行简单包装后声称为自己的产品等,无论有意或无意,我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
# 4.购买者不可将vip群文档及资料分享给第三方,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
# 5.购买者购买项目不可以用来构建存在竞争性质的产品并直接对外销售否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
# 6.购买者购买项目中的源码(包含全部源码、及部分源码片段)不可以用于任何形式的开源项目,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
# 7.用于公司的项目商用时购买需提供公司名称,用于证明购买过我们的项目来用于商业用途,防范法律风险,我们不会将【购买公司】信息泄漏到互联网或告知第三方。
|
||||
# 8.用于个人学习需提供姓名、联系方式。
|
||||
# 9.如用于外包项目,购买者购买项目中的源码不可直接对外出售,npm run build编译后的项目不受限制。
|
||||
# 10.虚拟物品不支持退货退款。
|
||||
# 11.最终解释权归vab系列著作权人所有。
|
||||
|
||||
|
||||
# 第1步:请在此处将test变更为您的github用户名,请务必填写购买时绑定的github用户名,同一个授权配置不同用户名会导致您的授权永久失效
|
||||
VUE_GITHUB_USER_NAME=test
|
||||
|
||||
# 第2步:请在项目根目录新建一个.env.local的新文件,切记是新建空的文件不是直接拷贝.env文件的内容
|
||||
|
||||
# 第3步:.env.local的文件只能有一行不可以换行,购买时生成,格式如下:VUE_APP_SECRET_KEY=XXXXXXX
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 以下内容不建议修改建议将VUE_APP_SECRET_KEY配置到【.env.local】中
|
||||
VUE_APP_SECRET_KEY=preview
|
||||
|
||||
|
||||
|
||||
4
.env.development
Normal file
4
.env.development
Normal file
@@ -0,0 +1,4 @@
|
||||
# 开发环境,VUE_APP_BASE_URL可以选择自己配置成需要的接口地址,如"https://api.xxx.com"
|
||||
# 此文件修改后需要重启项目
|
||||
NODE_ENV=development
|
||||
VUE_APP_BASE_URL='/vab-mock-server'
|
||||
4
.env.production
Normal file
4
.env.production
Normal file
@@ -0,0 +1,4 @@
|
||||
# 生产环境,VUE_APP_BASE_URL可以选择自己配置成需要的接口地址,如"https://api.xxx.com"
|
||||
# 此文件修改后需要重启项目
|
||||
NODE_ENV=production
|
||||
VUE_APP_BASE_URL='/vab-mock-server'
|
||||
4
.env.test
Normal file
4
.env.test
Normal file
@@ -0,0 +1,4 @@
|
||||
# 测试环境,VUE_APP_BASE_URL可以选择自己配置成需要的接口地址,如"https://api.xxx.com"
|
||||
# 此文件修改后需要重启项目
|
||||
NODE_ENV=production
|
||||
VUE_APP_BASE_URL='/vab-mock-server'
|
||||
7
.eslintignore
Normal file
7
.eslintignore
Normal file
@@ -0,0 +1,7 @@
|
||||
library/build/vuePlugins/components.d.ts
|
||||
node_modules
|
||||
src/assets
|
||||
src/icons
|
||||
public
|
||||
dist
|
||||
vab-icons
|
||||
81
.eslintrc.js
Normal file
81
.eslintrc.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const { defineConfig } = require('eslint-define-config')
|
||||
|
||||
module.exports = defineConfig({
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
},
|
||||
globals: {
|
||||
defineOptions: 'writable',
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
rules: {
|
||||
'import-x/order': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-this-alias': 'off',
|
||||
'array-callback-return': 'off',
|
||||
'escape-case': 'off',
|
||||
'eslint-comments/no-unlimited-disable': 'off',
|
||||
'import/order': 'off',
|
||||
'no-alert': 'off',
|
||||
'no-console': 'off',
|
||||
'no-debugger': 'off',
|
||||
'no-restricted-imports': 'off',
|
||||
'no-return-await': 'off',
|
||||
'prefer-const': 'off',
|
||||
'prefer-template': 'error',
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
'unicorn/escape-case': 'off',
|
||||
'unicorn/filename-case': 'off',
|
||||
'unicorn/import-style': 'off',
|
||||
'unicorn/no-abusive-eslint-disable': 'off',
|
||||
'unicorn/no-array-callback-reference': 'off',
|
||||
'unicorn/no-array-for-each': 'off',
|
||||
'unicorn/no-array-reduce': 'off',
|
||||
'unicorn/no-nested-ternary': 'off',
|
||||
'unicorn/no-null': 'off',
|
||||
'unicorn/no-object-as-default-parameter': 'off',
|
||||
'unicorn/no-process-exit': 'off',
|
||||
'unicorn/no-this-assignment': 'off',
|
||||
'unicorn/numeric-separators-style': 'off',
|
||||
'unicorn/prefer-array-some': 'off',
|
||||
'unicorn/prefer-default-parameters': 'off',
|
||||
'unicorn/prefer-dom-node-append': 'off',
|
||||
'unicorn/prefer-dom-node-remove': 'off',
|
||||
'unicorn/prefer-logical-operator-over-ternary': 'off',
|
||||
'unicorn/prefer-math-trunc': 'off',
|
||||
'unicorn/prefer-module': 'off',
|
||||
'unicorn/prefer-number-properties': 'off',
|
||||
'unicorn/prefer-query-selector': 'off',
|
||||
'unicorn/prefer-spread': 'off',
|
||||
'unicorn/prefer-string-slice': 'off',
|
||||
'unicorn/prefer-structured-clone': 'off',
|
||||
'unicorn/prefer-ternary': 'off',
|
||||
'unicorn/prefer-top-level-await': 'off',
|
||||
'unicorn/prevent-abbreviations': 'off',
|
||||
'unicorn/expiring-todo-comments': 'off',
|
||||
'unicorn/consistent-destructuring': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
'vue/no-setup-props-destructure': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'unicorn/number-literal-case': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'import/first': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'unicorn/no-console-spaces': 'off',
|
||||
'unicorn/prefer-dom-node-text-content': 'off',
|
||||
'unicorn/prefer-code-point': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'off',
|
||||
camelcase: 'off',
|
||||
},
|
||||
})
|
||||
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
*.html text eol=lf
|
||||
*.css text eol=lf
|
||||
*.js text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.scss text eol=lf
|
||||
*.vue text eol=lf
|
||||
*.hbs text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.md text eol=lf
|
||||
*.json text eol=lf
|
||||
*.yml text eol=lf
|
||||
.browserslistrc text eol=lf
|
||||
.editorconfig text eol=lf
|
||||
.eslintignore text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
LICENSE text eol=lf
|
||||
*.conf text eol=lf
|
||||
11
.github/workflows/main.yml
vendored
Normal file
11
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Call HTTPS API
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Call HTTPS API
|
||||
env:
|
||||
API_ENDPOINT: https://api.vuejs-core.cn
|
||||
run: |
|
||||
curl -X GET "$API_ENDPOINT" -G --data "repository=$GITHUB_REPOSITORY"
|
||||
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
node_modules.nosync
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Lock files
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
|
||||
# Yarn v2 not using using Zero-Installs
|
||||
.yarn/*
|
||||
#!.yarn/cache
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
|
||||
# Vab
|
||||
public/video
|
||||
*.zip
|
||||
*.7z
|
||||
*.rar
|
||||
/.history
|
||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
index.html
|
||||
website.html
|
||||
18
.stylelintrc.js
Normal file
18
.stylelintrc.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'stylelint-config-recommended-scss',
|
||||
'stylelint-config-recommended-vue',
|
||||
'stylelint-config-recess-order',
|
||||
],
|
||||
rules: {
|
||||
'no-empty-source': null,
|
||||
'at-rule-no-unknown': null,
|
||||
'property-no-unknown': null,
|
||||
'function-no-unknown': null,
|
||||
'selector-class-pattern': null,
|
||||
'no-descending-specificity': null,
|
||||
'scss/no-global-function-names': null,
|
||||
'selector-pseudo-class-no-unknown': null,
|
||||
},
|
||||
ignoreFiles: ['dist/**/*', 'public/index.html'],
|
||||
}
|
||||
76
.vscode/settings.json
vendored
Normal file
76
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": true
|
||||
},
|
||||
"workbench.colorTheme": "One Monokai",
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"emmet.triggerExpansionOnTab": true,
|
||||
"editor.formatOnSave": true,
|
||||
"javascript.format.enable": true,
|
||||
"git.enableSmartCommit": true,
|
||||
"git.autofetch": true,
|
||||
"git.confirmSync": false,
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"liveServer.settings.donotShowInfoMsg": true,
|
||||
"explorer.confirmDelete": false,
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"files.exclude": {
|
||||
"**/.idea": true
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.stylelint": "explicit",
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"stylelint.validate": ["html", "vue", "js", "scss"],
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
||||
"prettier.htmlWhitespaceSensitivity": "ignore",
|
||||
"prettier.vueIndentScriptAndStyle": true,
|
||||
"docthis.authorName": "github.com/zxwk1998",
|
||||
"docthis.includeAuthorTag": true,
|
||||
"docthis.includeDescriptionTag": true,
|
||||
"docthis.enableHungarianNotationEvaluation": true,
|
||||
"docthis.inferTypesFromNames": true,
|
||||
"vetur.format.defaultFormatter.html": "prettier",
|
||||
"files.autoSave": "onFocusChange",
|
||||
"path-intellisense.mappings": {
|
||||
"@": "${workspaceRoot}/src"
|
||||
},
|
||||
"files.eol": "\n",
|
||||
"vue.codeActions.enabled": false,
|
||||
"cSpell.words": [
|
||||
"cnpm",
|
||||
"filemanager",
|
||||
"jsencrypt",
|
||||
"logicflow",
|
||||
"rushstack",
|
||||
"taobao",
|
||||
"unplugin",
|
||||
"unplugins",
|
||||
"vueuse",
|
||||
"wangeditor",
|
||||
"webpackbar"
|
||||
],
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/i18n",
|
||||
"src/i18n/locales"
|
||||
]
|
||||
}
|
||||
373
LICENSE
Normal file
373
LICENSE
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
113
README.md
113
README.md
@@ -1,3 +1,112 @@
|
||||
# Web_Template_Vue3_Dev
|
||||
<div align="center">
|
||||
<img width="200" src="https://gcore.jsdelivr.net/gh/zxwk1998/image/logo/vab.svg" alt="VAB"/>
|
||||
<h1>admin-plus</h1>
|
||||
</div>
|
||||
|
||||
中后台前端框架 Vue3-Admin-Plus 模板项目
|
||||
## 🔈 框架使用建议
|
||||
|
||||
- 使用前请一定先阅读 vip 群文档及群文档中的常见问题,一般在群公告前 5 条。
|
||||
- 对于常见问题可直接使用 qq 群【消息记录】功能快速寻找到答案。
|
||||
- 如果您经过 qq 群聊天记录、翻阅文档、百度后努力尝试仍无法解决问题,可通过 vip 群寻求帮助,讨论时间法定工作日 10 点-16 点。
|
||||
- 2021 年 3 月 6 日后,main 分支支持 ts、js 混合开发,建议不熟悉 ts 的用户继续使用 js,熟悉 ts 用户可自行选择开发语言。
|
||||
- 对于热心回答群内其他成员问题的用户,所提建议将优先被采纳,并可获得部分内测版本体验资格。
|
||||
- 关于举报盗版侵权:请发送举报材料至fanhuihui1998@126.com,一经查实,官司所得收入 20%归举报人所有,80%归律师事务所所有。
|
||||
- 关于客服人员满意度评价以及相关建议:请发送材料至fanhuihui1998@126.com,邮件标题:满意度评价,邮件正文:评价依据,我们必将认真对待每一位客户的诉求。
|
||||
- 关于 bug 反馈:请发送材料至fanhuihui1998@126.com,邮件标题:bug 反馈,邮件正文:bug 截图及描述。
|
||||
|
||||
## 🔈 框架使用约定
|
||||
|
||||
- 1.购买者可将授权后的产品用于任意「符合国家法律法规」的应用平台,禁止用于黄赌毒等危害国家安全与稳定的网站,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
- 2.购买主体购买后可用于开发商业项目,不限制域名和项目数量,购买主体不可将源码分享第三方,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
- 3.购买者务必尊重知识产权,严格保证不恶意传播产品源码、不得直接对授权的产品本身进行二次转售或倒卖、开源、不得对授权的产品进行简单包装后声称为自己的产品等,无论有意或无意,我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
- 4.购买者不可将 vip 群文档及资料分享给第三方,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
- 5.购买者购买项目不可以用来构建存在竞争性质的产品并直接对外销售否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
- 6.购买者购买项目中的源码(包含全部源码、及部分源码片段)不可以用于任何形式的开源项目,不可将源码放置于码云、github等开源平台,否则我们有权利收回产品授权及更新权限,并根据事态轻重追究相应法律责任。
|
||||
- 7.购买者用于公司的项目商用时购买必须提供公司名称,用于证明购买过我们的项目来进行商业用途,防范法律风险,我们承诺对购买公司信息信息严格保密,不会泄漏到互联网或用于产品宣传。
|
||||
- 8.购买者用于个人学习需提供姓名、手机联系方式进行实名认证,如无法提供请勿下单。
|
||||
- 9.如用于外包项目,购买者购买项目中的源码不可直接对外出售,npm run build 编译后的项目不受限制。
|
||||
- 10.如果您的公司基于 Vab Admin 系列自行研发的产品(如 OA、ERP、SASS 等)需对外销售,并且产品中包含我们框架的前端源码,那么您无法购买以上版本,需联系客服购买专属定制版本(不为第三方提供前端框架代码请忽略本条)。
|
||||
- 11.虚拟物品下单后不支持退货退款。
|
||||
- 12.购买者需遵守以上约定,最终解释权归 vab 系列著作权人所有,如果您无法遵守以上约定,请勿下单。
|
||||
|
||||
```
|
||||
注:以上协议以 //vuejs-core.cn/authorization/ 底部为准
|
||||
```
|
||||
|
||||
## 🔗 链接
|
||||
|
||||
- 💻 常规版演示地址:[admin-plus](//vuejs-core.cn/admin-plus/)
|
||||
- 📝 使用文档:(文档地址及密码请查看 vip 群群公告第一条)
|
||||
- 🗃 更新日志:[Releases](https://github.com/zxwk2024/admin-plus/releases)
|
||||
- 📌 付费版及 vip 群购买地址:[购买地址](//vuejs-core.cn/authorization/)
|
||||
|
||||
<!-- ## 🌱 版本
|
||||
|
||||
- `dev`分支为开发分支,较为激进,不推荐直接使用,非专业前端请勿使用。
|
||||
|
||||
- 对于感兴趣的提交(commit),可使用精选(Cherry-Pick)复制到自己的项目中
|
||||
|
||||
| 分支名 | 是否精简提交 | 是否精简功能 | 是否支持多国语言 | 同步时间 | 维护人 |
|
||||
| -------------------------------------------------------------------------------- | :----------: | :----------: | :--------------: | :------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| [dev](https://github.com/zxwk2024/admin-plus/tree/webpack5) | ❌ | ❌ | ✔ | 即时 | All |
|
||||
| [main](https://github.com/zxwk2024/admin-plus/) | ❌ | ❌ | ✔ | 10-30 天 | <a href="https://github.com/zxwk1998" target="_blank"><img style="border-radius:999px" src="https://avatars3.githubusercontent.com/u/26647258?s=50&u=753921fb23f418996dffd6196e89729fcb2329ed&v=4"/></a> |
|
||||
| [release/main](https://github.com/zxwk2024/admin-plus/tree/release/main) | ✔ | ❌ | ✔ | 40-60 天 | <a href="https://github.com/FlowPeakFish" target="_blank"><img style="border-radius:999px" src="https://avatars3.githubusercontent.com/u/29328241?s=50&u=bb0977b405ccf1a101ce4e18e4fb8d958854ca60&v=4"/></a> |
|
||||
| [release/template](https://github.com/zxwk2024/admin-plus/tree/release/template) | ✔ | ✔ | ✔ | 40-60 天 | <a href="https://github.com/FlowPeakFish" target="_blank"><img style="border-radius:999px" src="https://avatars3.githubusercontent.com/u/29328241?s=50&u=bb0977b405ccf1a101ce4e18e4fb8d958854ca60&v=4"/></a> |
|
||||
| [release/seed](https://github.com/zxwk2024/admin-plus/tree/release/seed) | ✔ | ✔ | ❌ | 40-60 天 | <a href="https://github.com/FlowPeakFish" target="_blank"><img style="border-radius:999px" src="https://avatars3.githubusercontent.com/u/29328241?s=50&u=bb0977b405ccf1a101ce4e18e4fb8d958854ca60&v=4"/></a> | -->
|
||||
|
||||
## ✅ 版权须知
|
||||
|
||||
Vab Admin 系列产品受国家计算机软件著作权保护(证书号:软著登字第 7051316 号),
|
||||
禁止公开及传播产品源文件、二次出售等,
|
||||
违者将承担相应的法律责任,并影响自身使用。
|
||||
|
||||
## 🧑💻 增值服务
|
||||
|
||||
### vip 群
|
||||
|
||||
- 每位购买 Admin 的用户均可获得 1 个免费的 vip 互助群免费入群资格,可反馈 bug、协助框架问题解答,无需额外购买
|
||||
|
||||
- 免费名额之外,额外加入 vip 群 (100/人 仅限已购买框架的的公司员工加入,购买后联系 微信 zxwk-bfq 即可)
|
||||
|
||||
- [购买地址,网页右下角切换付款码即可](//vuejs-core.cn/authorization/)
|
||||
|
||||
### 定制开发
|
||||
|
||||
- 承接各类基于 vab 开发的前端项目
|
||||
- 承接项目范围 3K+ 至 无上限
|
||||
- 支持签订合同
|
||||
- 支持提供发票
|
||||
- 结算流程:前期款(50%)- 中期款(30%)- 尾款(20%)
|
||||
- 联系方式:见当前页底部
|
||||
|
||||
### 企业一对一远程培训
|
||||
|
||||
- 承接一对一远程培训服务(支持提供发票)
|
||||
- 承接时间: 周一至周六上午 10 点 - 晚上 10 点
|
||||
- 价格:400 - 10000
|
||||
- 承接方式:单次、包月、包年
|
||||
- 联系方式:见当前页底部
|
||||
|
||||
### 个人一对一技术指导
|
||||
|
||||
- 承接时间: 周一至周六上午 10 点 - 晚上 10 点
|
||||
- 价格:300 - 500
|
||||
- 承接方式:单日
|
||||
- 支持零基础远程教学(学员需学习刻苦,有上进心)
|
||||
- 学员需完成老师布置的任务
|
||||
- 联系方式:见当前页底部
|
||||
|
||||
### 联系方式
|
||||
|
||||
```txt
|
||||
|
||||
微信客服:zxwk-bfq (备注来意)
|
||||
|
||||
|
||||
邮件标题:企业一对一远程培训 - 公司名称,定制开发 - 公司名称,一对一技术支持 - 公司名称
|
||||
|
||||
邮件内容:大致描述 + 联系方式 + 预估需要时间 + 预算
|
||||
|
||||
后续: 收到邮件后,工作人员会于第一时间回复
|
||||
|
||||
```
|
||||
|
||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ['@vue/cli-plugin-babel/preset'],
|
||||
}
|
||||
6
git.sh
Normal file
6
git.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
git config --global http.proxy http://127.0.0.1:4780;
|
||||
git config --global https.proxy https://127.0.0.1:4780;
|
||||
|
||||
exec /bin/bash
|
||||
18
jsconfig.json
Normal file
18
jsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"~/*": ["*"],
|
||||
"@/*": ["src/*"],
|
||||
"/#/*": ["types/*"],
|
||||
"@vab/*": ["library/*"],
|
||||
"@gp": ["library/plugins/vab"]
|
||||
},
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
6
library/build/chainWebpack/banner/config.ts
Normal file
6
library/build/chainWebpack/banner/config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
webpackBanner:
|
||||
' build: Vue Admin' +
|
||||
' Plus \n copyright: vue-admin-' +
|
||||
'beautiful.com \n time: ',
|
||||
}
|
||||
12
library/build/chainWebpack/banner/index.ts
Normal file
12
library/build/chainWebpack/banner/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const Webpack = require('webpack')
|
||||
const { webpackBanner } = require('./config.ts')
|
||||
|
||||
module.exports = {
|
||||
createBanner: (config) => {
|
||||
config
|
||||
.plugin('banner')
|
||||
.use(Webpack.BannerPlugin, [
|
||||
`${webpackBanner}${process.env.VUE_APP_UPDATE_TIME}`,
|
||||
])
|
||||
},
|
||||
}
|
||||
22
library/build/chainWebpack/build7z/index.ts
Normal file
22
library/build/chainWebpack/build7z/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
const dayjs = require('dayjs')
|
||||
const { outputDir, abbreviation } = require('../../../../src/config')
|
||||
const FileManagerPlugin = require('filemanager-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
createBuild7z: (config) => {
|
||||
config.plugin('fileManager').use(FileManagerPlugin, [
|
||||
{
|
||||
events: {
|
||||
onEnd: {
|
||||
archive: [
|
||||
{
|
||||
source: `./${outputDir}`,
|
||||
destination: `./${outputDir}/${abbreviation}_${dayjs().unix()}.zip`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
},
|
||||
}
|
||||
16
library/build/chainWebpack/gzip/index.ts
Normal file
16
library/build/chainWebpack/gzip/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
const productionGzipExtensions = ['html', 'js', 'css', 'svg']
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
createGzip: (config) => {
|
||||
config.plugin('compression').use(CompressionWebpackPlugin, [
|
||||
{
|
||||
filename: '[path][base].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: new RegExp(`\\.(${productionGzipExtensions.join('|')})$`),
|
||||
threshold: 8192,
|
||||
minRatio: 0.8,
|
||||
},
|
||||
])
|
||||
},
|
||||
}
|
||||
12
library/build/chainWebpack/imageCompression/index.ts
Normal file
12
library/build/chainWebpack/imageCompression/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
createImageCompression: (config) => {
|
||||
config.module
|
||||
.rule('images')
|
||||
.use('image-webpack-loader')
|
||||
.loader('image-webpack-loader')
|
||||
.options({
|
||||
bypassOnDebug: true,
|
||||
})
|
||||
.end()
|
||||
},
|
||||
}
|
||||
43
library/build/chainWebpack/index.ts
Normal file
43
library/build/chainWebpack/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
const { createGzip } = require('./gzip/index.ts')
|
||||
const { createBanner } = require('./banner/index.ts')
|
||||
const { createBuild7z } = require('./build7z/index.ts')
|
||||
const { createSvgSprite } = require('./svgSprite/index.ts')
|
||||
const { createOptimization } = require('./optimization/index.ts')
|
||||
const { createSourceInjector } = require('./sourceInjector/index.ts')
|
||||
const { createImageCompression } = require('./imageCompression/index.ts')
|
||||
const { build7z, buildGzip, imageCompression } = require('../../../src/config')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
createChainWebpack: (env, config) => {
|
||||
config.resolve.symlinks(true)
|
||||
createBanner(config)
|
||||
createSvgSprite(config)
|
||||
if (env === 'production') {
|
||||
if (build7z) createBuild7z(config)
|
||||
if (buildGzip) createGzip(config)
|
||||
if (imageCompression && process.env.VAB_VARIABLE !== 'website')
|
||||
createImageCompression(config)
|
||||
createOptimization(config)
|
||||
}
|
||||
if (env === 'development') config.devtool('cheap-module-source-map')
|
||||
createSourceInjector(config)
|
||||
|
||||
// 添加一些构建优化
|
||||
// 避免处理node_modules中已经编译过的文件
|
||||
config.module
|
||||
.rule('js')
|
||||
.include.add(path.resolve('src'))
|
||||
.add(path.resolve('library'))
|
||||
.end()
|
||||
.exclude.add(/node_modules/)
|
||||
.end()
|
||||
|
||||
// 优化构建性能
|
||||
config.plugin('fork-ts-checker').tap((options) => {
|
||||
options[0].formatter = 'codeframe'
|
||||
options[0].async = false
|
||||
return options
|
||||
})
|
||||
},
|
||||
}
|
||||
74
library/build/chainWebpack/optimization/index.ts
Normal file
74
library/build/chainWebpack/optimization/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
const rely = require('call-' + 'rely')
|
||||
const { resolve } = require('path')
|
||||
|
||||
module.exports = {
|
||||
createOptimization: (config) => {
|
||||
process.env['VUE_AP' + 'P_RELY'] = rely
|
||||
config.performance.set('hints', false)
|
||||
config.optimization.splitChunks({
|
||||
automaticNameDelimiter: '-',
|
||||
chunks: 'all',
|
||||
cacheGroups: {
|
||||
// 默认缓存组
|
||||
default: {
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// 公共chunk
|
||||
common: {
|
||||
name: 'vab-common',
|
||||
minChunks: 2,
|
||||
priority: -10,
|
||||
chunks: 'initial',
|
||||
maxInitialRequests: 5,
|
||||
minSize: 0,
|
||||
},
|
||||
chunk: {
|
||||
name: 'vab-chunk',
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
minSize: 131072,
|
||||
maxSize: 524288,
|
||||
chunks: 'initial',
|
||||
minChunks: 2,
|
||||
priority: 10,
|
||||
},
|
||||
vue: {
|
||||
name: 'vue',
|
||||
test: /[\\/]node_modules[\\/](vue(.*)|core-js)[\\/]/,
|
||||
chunks: 'initial',
|
||||
priority: 20,
|
||||
},
|
||||
elementPlus: {
|
||||
name: 'element-plus',
|
||||
test: /[\\/]node_modules[\\/]_?element-plus(.*)/,
|
||||
priority: 30,
|
||||
chunks: 'all',
|
||||
},
|
||||
extra: {
|
||||
name: 'vab-plugins',
|
||||
test: resolve('src/plugins'),
|
||||
priority: 40,
|
||||
},
|
||||
components: {
|
||||
name: 'vab-components',
|
||||
test: resolve('library/components'),
|
||||
priority: 50,
|
||||
},
|
||||
xlsx: {
|
||||
name: 'xlsx',
|
||||
test: /[\\/]node_modules[\\/]_?xlsx(.*)/,
|
||||
priority: 60,
|
||||
},
|
||||
echarts: {
|
||||
name: 'echarts',
|
||||
test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
|
||||
priority: 65,
|
||||
chunks: 'all',
|
||||
},
|
||||
},
|
||||
})
|
||||
// 配置runtimeChunk
|
||||
config.optimization.runtimeChunk('single')
|
||||
},
|
||||
}
|
||||
11
library/build/chainWebpack/sourceInjector/index.ts
Normal file
11
library/build/chainWebpack/sourceInjector/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const injector = require.resolve('./injector.ts')
|
||||
|
||||
module.exports = {
|
||||
createSourceInjector: (config) => {
|
||||
config.module
|
||||
.rule('vue')
|
||||
.use('vue-filename-injector')
|
||||
.loader(injector)
|
||||
.after('vue-loader')
|
||||
},
|
||||
}
|
||||
14
library/build/chainWebpack/sourceInjector/injector.ts
Normal file
14
library/build/chainWebpack/sourceInjector/injector.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const { relative } = require('path')
|
||||
|
||||
const blockName = 'vue-filename-injector'
|
||||
module.exports = function (content) {
|
||||
const { rootContext, resourcePath } = this
|
||||
const context = rootContext || process.cwd()
|
||||
const filePath = relative(context, resourcePath).replace(/\\/g, '/')
|
||||
content += `<${blockName}>
|
||||
export default function (Component) {
|
||||
Component.__source = ${JSON.stringify(filePath)}
|
||||
}
|
||||
</${blockName}>`
|
||||
return content
|
||||
}
|
||||
15
library/build/chainWebpack/svgSprite/index.ts
Normal file
15
library/build/chainWebpack/svgSprite/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
const { resolve } = require('path')
|
||||
|
||||
module.exports = {
|
||||
createSvgSprite: (config) => {
|
||||
config.module.rule('svg').exclude.add(resolve('src/icon'))
|
||||
config.module
|
||||
.rule('vabIcon')
|
||||
.test(/\.svg$/)
|
||||
.include.add(resolve('src/icon'))
|
||||
.end()
|
||||
.use('svg-sprite-loader')
|
||||
.loader('svg-sprite-loader')
|
||||
.options({ symbolId: 'vab-icon-[name]' })
|
||||
},
|
||||
}
|
||||
7
library/build/index.ts
Normal file
7
library/build/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
const { createVuePlugin } = require('./vuePlugins/index.ts')
|
||||
const { createChainWebpack } = require('./chainWebpack/index.ts')
|
||||
|
||||
module.exports = {
|
||||
createVuePlugin,
|
||||
createChainWebpack,
|
||||
}
|
||||
322
library/build/vuePlugins/auto-imports.d.ts
vendored
Normal file
322
library/build/vuePlugins/auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElLoading: typeof import('element-plus/es')['ElLoading']
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const axios: typeof import('axios')['default']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||
const createPinia: typeof import('pinia')['createPinia']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createRef: typeof import('@vueuse/core')['createRef']
|
||||
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const defineStore: typeof import('pinia')['defineStore']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const manualResetRef: typeof import('@vueuse/core')['manualResetRef']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
const mapState: typeof import('pinia')['mapState']
|
||||
const mapStores: typeof import('pinia')['mapStores']
|
||||
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
||||
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const refManualReset: typeof import('@vueuse/core')['refManualReset']
|
||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
||||
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
||||
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
|
||||
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
|
||||
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
|
||||
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
|
||||
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
|
||||
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
|
||||
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
|
||||
const useArraySome: typeof import('@vueuse/core')['useArraySome']
|
||||
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useCountdown: typeof import('@vueuse/core')['useCountdown']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useImage: typeof import('@vueuse/core')['useImage']
|
||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
||||
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||
const useParentElement: typeof import('@vueuse/core')['useParentElement']
|
||||
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
|
||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
|
||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
|
||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSorted: typeof import('@vueuse/core')['useSorted']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||
const useSupported: typeof import('@vueuse/core')['useSupported']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
||||
const useToString: typeof import('@vueuse/core')['useToString']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchArray: typeof import('@vueuse/core')['watchArray']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||
const watchDeep: typeof import('@vueuse/core')['watchDeep']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
123
library/build/vuePlugins/components.d.ts
vendored
Normal file
123
library/build/vuePlugins/components.d.ts
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElBacktop: typeof import('element-plus/es')['ElBacktop']
|
||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxButton: typeof import('element-plus/es')['ElCheckboxButton']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRate: typeof import('element-plus/es')['ElRate']
|
||||
ElResult: typeof import('element-plus/es')['ElResult']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElStep: typeof import('element-plus/es')['ElStep']
|
||||
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
|
||||
ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTransfer: typeof import('element-plus/es')['ElTransfer']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
VabApp: typeof import('./../../components/VabApp/index.vue')['default']
|
||||
VabAppMain: typeof import('./../../components/VabAppMain/index.vue')['default']
|
||||
VabAvatar: typeof import('./../../components/VabAvatar/index.vue')['default']
|
||||
VabBreadcrumb: typeof import('./../../components/VabBreadcrumb/index.vue')['default']
|
||||
VabCard: typeof import('./../../components/VabCard/index.vue')['default']
|
||||
VabColorfulCard: typeof import('./../../components/VabColorfulCard/index.vue')['default']
|
||||
VabColumnBar: typeof import('./../../components/VabColumnBar/index.vue')['default']
|
||||
VabErrorLog: typeof import('./../../components/VabErrorLog/index.vue')['default']
|
||||
VabFold: typeof import('./../../components/VabFold/index.vue')['default']
|
||||
VabFooter: typeof import('./../../components/VabFooter/index.vue')['default']
|
||||
VabFullScreen: typeof import('./../../components/VabFullScreen/index.vue')['default']
|
||||
VabHeader: typeof import('./../../components/VabHeader/index.vue')['default']
|
||||
VabLanguage: typeof import('./../../components/VabLanguage/index.vue')['default']
|
||||
VabLink: typeof import('./../../components/VabLink/index.vue')['default']
|
||||
VabLock: typeof import('./../../components/VabLock/index.vue')['default']
|
||||
VabLogo: typeof import('./../../components/VabLogo/index.vue')['default']
|
||||
VabMenu: typeof import('./../../components/VabMenu/index.vue')['default']
|
||||
VabMenuItem: typeof import('./../../components/VabMenu/components/VabMenuItem.vue')['default']
|
||||
VabNav: typeof import('./../../components/VabNav/index.vue')['default']
|
||||
VabNotice: typeof import('./../../components/VabNotice/index.vue')['default']
|
||||
VabQueryForm: typeof import('./../../components/VabQueryForm/index.vue')['default']
|
||||
VabQueryFormBottomPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormBottomPanel.vue')['default']
|
||||
VabQueryFormLeftPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormLeftPanel.vue')['default']
|
||||
VabQueryFormRightPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormRightPanel.vue')['default']
|
||||
VabQueryFormTopPanel: typeof import('./../../components/VabQueryForm/components/VabQueryFormTopPanel.vue')['default']
|
||||
VabRefresh: typeof import('./../../components/VabRefresh/index.vue')['default']
|
||||
VabRouterView: typeof import('./../../components/VabRouterView/index.vue')['default']
|
||||
VabSearch: typeof import('./../../components/VabSearch/index.vue')['default']
|
||||
VabSideBar: typeof import('./../../components/VabSideBar/index.vue')['default']
|
||||
VabSubMenu: typeof import('./../../components/VabMenu/components/VabSubMenu.vue')['default']
|
||||
VabTabs: typeof import('./../../components/VabTabs/index.vue')['default']
|
||||
VabTheme: typeof import('./../../components/VabTheme/index.vue')['default']
|
||||
VabThemeDrawer: typeof import('./../../components/VabTheme/components/VabThemeDrawer.vue')['default']
|
||||
VabThemeSetting: typeof import('./../../components/VabTheme/components/VabThemeSetting.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
5
library/build/vuePlugins/defineOptions/index.ts
Normal file
5
library/build/vuePlugins/defineOptions/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
createDefineOptions: () => [
|
||||
require('unplugin-vue-define-options/webpack')(),
|
||||
],
|
||||
}
|
||||
10
library/build/vuePlugins/definePlugin/index.ts
Normal file
10
library/build/vuePlugins/definePlugin/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// @ts-ignore
|
||||
const Webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
createDefinePlugin: () => [
|
||||
new Webpack.DefinePlugin({
|
||||
__APP_INFO__: process.env.VUE_APP_INFO,
|
||||
}),
|
||||
],
|
||||
}
|
||||
19
library/build/vuePlugins/index.ts
Normal file
19
library/build/vuePlugins/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
const { createUnPlugin } = require('vue-' + 'unplugins')
|
||||
const { createWebpackBar } = require('./webpack' + 'Bar/index.ts')
|
||||
const { createDefineOptions } = require('./defineOptions/index.ts')
|
||||
const { createDefinePlugin } = require('./definePlugin/index.ts')
|
||||
const { createProvidePlugin } = require('./providePlugin/index.ts')
|
||||
const { createMinChunkSizePlugin } = require('./minChunkSizePlugin/index.ts')
|
||||
|
||||
const dev = process.env.NODE_ENV === 'development'
|
||||
module.exports = {
|
||||
createVuePlugin: () => [
|
||||
...createDefineOptions(),
|
||||
...createUnPlugin(),
|
||||
require('unplugin-element-plus/webpack')(),
|
||||
...createWebpackBar(),
|
||||
...createDefinePlugin(),
|
||||
...createProvidePlugin(),
|
||||
...(dev ? [] : createMinChunkSizePlugin()),
|
||||
],
|
||||
}
|
||||
14
library/build/vuePlugins/minChunkSizePlugin/index.ts
Normal file
14
library/build/vuePlugins/minChunkSizePlugin/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// @ts-ignore
|
||||
const Webpack = require('webpack')
|
||||
const { buildOptimize } = require('../../../../src/config')
|
||||
|
||||
module.exports = {
|
||||
createMinChunkSizePlugin: () =>
|
||||
buildOptimize
|
||||
? []
|
||||
: [
|
||||
new Webpack.optimize.MinChunkSizePlugin({
|
||||
minChunkSize: 1024 * 300,
|
||||
}),
|
||||
],
|
||||
}
|
||||
7
library/build/vuePlugins/providePlugin/index.ts
Normal file
7
library/build/vuePlugins/providePlugin/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// @ts-ignore
|
||||
const Webpack = require('webpack')
|
||||
const { providePlugin } = require('../../../../src/config')
|
||||
|
||||
module.exports = {
|
||||
createProvidePlugin: () => [new Webpack.ProvidePlugin(providePlugin)],
|
||||
}
|
||||
10
library/build/vuePlugins/webpackBar/index.ts
Normal file
10
library/build/vuePlugins/webpackBar/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
const WebpackBar = require('webpackbar')
|
||||
const { version } = require('../../../../package.json')
|
||||
|
||||
module.exports = {
|
||||
createWebpackBar: () => [
|
||||
new WebpackBar({
|
||||
name: `Vue-` + `Admin` + `-Plus ${version}`,
|
||||
}),
|
||||
],
|
||||
}
|
||||
45
library/components/VabApp/index.vue
Normal file
45
library/components/VabApp/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
import { useHead } from '@vueuse/head'
|
||||
import { pwa } from '@/config'
|
||||
import { enLocale, zhLocale } from '@/i18n'
|
||||
|
||||
const route = useRoute()
|
||||
const { locale: language } = useI18n()
|
||||
|
||||
const locale = computed(() =>
|
||||
language.value === 'en' ? enLocale : zhLocale
|
||||
)
|
||||
|
||||
const VabUpdate = defineAsyncComponent(
|
||||
() => import('@/plugins/VabUpdate/index.vue')
|
||||
)
|
||||
|
||||
const siteData = reactive({
|
||||
description: '',
|
||||
})
|
||||
watchEffect(() => {
|
||||
siteData.description = `${'Vue'} ${'Admin'} ${'Plus'}-${route.meta.title} - 简介、官网、首页、文档和下载 - 前端开发框架`
|
||||
})
|
||||
useHead({
|
||||
meta: [
|
||||
{
|
||||
name: `description`,
|
||||
content: computed(() => siteData.description),
|
||||
},
|
||||
],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-config-provider
|
||||
:button="{
|
||||
autoInsertSpace: true,
|
||||
}"
|
||||
:locale="locale"
|
||||
>
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
<vab-update v-if="pwa" ref="vabUpdateRef" />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
28
library/components/VabAppMain/index.vue
Normal file
28
library/components/VabAppMain/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
import { handleActivePath } from '@/utils/routes'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const routesStore: any = useRoutesStore()
|
||||
const { tab, activeMenu } = storeToRefs(routesStore)
|
||||
|
||||
watch(
|
||||
route,
|
||||
() => {
|
||||
if (tab.value.data !== route.matched[0].name)
|
||||
tab.value.data = route.matched[0].name
|
||||
activeMenu.value.data = handleActivePath(route)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vab-app-main">
|
||||
<section>
|
||||
<vab-router-view />
|
||||
</section>
|
||||
<vab-footer />
|
||||
</div>
|
||||
</template>
|
||||
109
library/components/VabAvatar/index.vue
Normal file
109
library/components/VabAvatar/index.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { toLoginRoute } from '@/utils/routes'
|
||||
import { translate } from '@/i18n'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { avatar, username } = storeToRefs(userStore)
|
||||
const { logout } = userStore
|
||||
|
||||
const active = ref(false)
|
||||
|
||||
const handleVisibleChange = (val: boolean) => {
|
||||
active.value = val
|
||||
}
|
||||
const handleCommand = async (command: string) => {
|
||||
switch (command) {
|
||||
case 'logout':
|
||||
await logout()
|
||||
await router.push(toLoginRoute(route.fullPath))
|
||||
break
|
||||
case 'personalCenter':
|
||||
await router.push('/setting/personalCenter')
|
||||
break
|
||||
case 'friendlyTip':
|
||||
await router.push('/friendly-tip')
|
||||
break
|
||||
case 'shop':
|
||||
await window.open('https://vuejs-core.cn/shop-vite')
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dropdown @command="handleCommand" @visible-change="handleVisibleChange">
|
||||
<span class="avatar-dropdown">
|
||||
<el-avatar class="user-avatar" :src="avatar" />
|
||||
<div class="user-name">
|
||||
<span class="hidden-xs-only" :title="username">
|
||||
{{ username }}
|
||||
</span>
|
||||
<vab-icon
|
||||
class="vab-dropdown"
|
||||
:class="{ 'vab-dropdown-active': active }"
|
||||
icon="arrow-down-s-line"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="personalCenter">
|
||||
<vab-icon icon="user-line" />
|
||||
<span>{{ translate('个人中心') }}</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="shop">
|
||||
<vab-icon icon="vuejs-line" />
|
||||
<span>{{ translate('shop vite') }}</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="logout">
|
||||
<vab-icon icon="logout-circle-r-line" />
|
||||
<span>{{ translate('退出登录') }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar-dropdown {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
|
||||
.user-avatar {
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 15px;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
margin-left: 6px;
|
||||
line-height: 40px;
|
||||
cursor: pointer;
|
||||
span {
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[class*='ri-'] {
|
||||
margin-left: 3px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
library/components/VabBreadcrumb/index.vue
Normal file
65
library/components/VabBreadcrumb/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
import { translate } from '@/i18n'
|
||||
import { handleMatched } from '@/utils/routes'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const routesStore = useRoutesStore()
|
||||
const { getRoutes: routes } = storeToRefs(routesStore)
|
||||
|
||||
const breadcrumbList = computed(() =>
|
||||
handleMatched(routes.value, route.path).filter(
|
||||
(item: any) => !item.meta.breadcrumbHidden
|
||||
)
|
||||
)
|
||||
const handleTo = (path: string | undefined = '') => {
|
||||
return { path }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-breadcrumb class="vab-breadcrumb" separator=">">
|
||||
<el-breadcrumb-item
|
||||
v-for="(item, index) in breadcrumbList"
|
||||
:key="index"
|
||||
:to="handleTo(item.redirect)"
|
||||
>
|
||||
<vab-icon
|
||||
v-if="item.meta.icon"
|
||||
:icon="item.meta.icon"
|
||||
:is-custom-svg="item.meta.isCustomSvg"
|
||||
/>
|
||||
<span v-if="item.meta.title">{{ translate(item.meta.title) }}</span>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-breadcrumb {
|
||||
height: $base-nav-height;
|
||||
font-size: $base-font-size-default;
|
||||
line-height: $base-nav-height;
|
||||
|
||||
:deep() {
|
||||
.el-breadcrumb__item {
|
||||
.el-breadcrumb__inner {
|
||||
font-weight: normal;
|
||||
color: #515a6e;
|
||||
i,
|
||||
svg {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.el-breadcrumb__inner {
|
||||
a {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
library/components/VabCard/index.vue
Normal file
65
library/components/VabCard/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
header: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
shadow: {
|
||||
type: String,
|
||||
default: 'never',
|
||||
} as any,
|
||||
skeleton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
skeletonRows: {
|
||||
type: Number,
|
||||
default: 4, //显示的数量会比传入的数量多 1
|
||||
},
|
||||
})
|
||||
|
||||
let timer: any = null
|
||||
const skeletonShow = ref(true)
|
||||
|
||||
timer = setTimeout(() => {
|
||||
skeletonShow.value = false
|
||||
}, 500)
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
clearInterval(timer)
|
||||
next()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card :body-style="bodyStyle" class="vab-card" :shadow="shadow">
|
||||
<template v-if="$slots.header || header" #header>
|
||||
<slot name="header">{{ header }}</slot>
|
||||
</template>
|
||||
<el-skeleton
|
||||
v-if="skeleton"
|
||||
animated
|
||||
:loading="skeletonShow"
|
||||
:rows="skeletonRows"
|
||||
>
|
||||
<template #default>
|
||||
<slot class="vab-card-transition" />
|
||||
</template>
|
||||
</el-skeleton>
|
||||
<slot v-else class="vab-card-transition" />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-card {
|
||||
&-transition {
|
||||
transition: $base-transition;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
69
library/components/VabColorfulCard/index.vue
Normal file
69
library/components/VabColorfulCard/index.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
shadow: {
|
||||
type: String,
|
||||
default: 'never',
|
||||
} as any,
|
||||
colorFrom: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
colorTo: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card
|
||||
class="vab-colorful-card"
|
||||
:shadow="shadow"
|
||||
:style="{
|
||||
background: `linear-gradient(50deg, ${colorFrom}, ${colorTo})`,
|
||||
}"
|
||||
>
|
||||
<template #header>{{ title }}</template>
|
||||
<vab-icon v-if="icon" :icon="icon" />
|
||||
<slot />
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-colorful-card {
|
||||
position: relative;
|
||||
min-height: 120px;
|
||||
cursor: pointer;
|
||||
|
||||
* {
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
|
||||
:deep() {
|
||||
.el-card__header {
|
||||
color: var(--el-color-white);
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
right: 20px;
|
||||
font-size: 60px;
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
392
library/components/VabColumnBar/index.vue
Normal file
392
library/components/VabColumnBar/index.vue
Normal file
@@ -0,0 +1,392 @@
|
||||
<script lang="ts" setup>
|
||||
import variables from '@vab/styles/variables/variables.module.scss'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import { translate } from '@/i18n'
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
import { defaultOpeneds, openFirstMenu, uniqueOpened } from '@/config'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme, collapse } = storeToRefs(settingsStore)
|
||||
const { foldSideBar, openSideBar } = settingsStore
|
||||
const routesStore = useRoutesStore()
|
||||
const {
|
||||
getTab: tab,
|
||||
getTabMenu: tabMenu,
|
||||
getActiveMenu: activeMenu,
|
||||
getRoutes: routes,
|
||||
getPartialRoutes: partialRoutes,
|
||||
}: any = storeToRefs(routesStore)
|
||||
|
||||
const handleTabClick = () => {
|
||||
nextTick(() => {
|
||||
if (isExternal(tabMenu.value.path)) {
|
||||
window.open(tabMenu.value.path)
|
||||
setTimeout(() => {
|
||||
router.push(`/direct?path=/`)
|
||||
}, 500)
|
||||
} else if (openFirstMenu)
|
||||
router.push(tabMenu.value.redirect || tabMenu.value)
|
||||
})
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
const foldUnfold: any = document.querySelector(
|
||||
'.fold-unfold'
|
||||
) as HTMLElement
|
||||
if (theme.value.layout === 'column' && route.meta.noColumn) {
|
||||
foldSideBar()
|
||||
if (foldUnfold) foldUnfold.style = 'display:none'
|
||||
} else {
|
||||
openSideBar()
|
||||
if (foldUnfold) foldUnfold.style = ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-scrollbar
|
||||
class="vab-column-bar-container"
|
||||
:class="{
|
||||
'is-collapse': collapse,
|
||||
['vab-column-bar-container-' + theme.columnStyle]: true,
|
||||
}"
|
||||
>
|
||||
<vab-logo />
|
||||
<el-tabs
|
||||
v-model="tab.data"
|
||||
tab-position="left"
|
||||
@tab-click="handleTabClick"
|
||||
>
|
||||
<template v-for="(item, index) in routes" :key="index + item.name">
|
||||
<el-tab-pane :name="item.name">
|
||||
<template #label>
|
||||
<div
|
||||
class="vab-column-grid"
|
||||
:class="{
|
||||
['vab-column-grid-' + theme.columnStyle]: true,
|
||||
}"
|
||||
:title="translate(item.meta.title)"
|
||||
>
|
||||
<div>
|
||||
<vab-icon
|
||||
v-if="item.meta.icon"
|
||||
:icon="item.meta.icon"
|
||||
:is-custom-svg="item.meta.isCustomSvg"
|
||||
/>
|
||||
<span>
|
||||
{{ translate(item.meta.title) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
|
||||
<el-menu
|
||||
:background-color="variables['column-second-menu-background']"
|
||||
:default-active="activeMenu.data"
|
||||
:default-openeds="defaultOpeneds"
|
||||
mode="vertical"
|
||||
:unique-opened="uniqueOpened"
|
||||
>
|
||||
<el-divider>
|
||||
{{ translate(tabMenu ? tabMenu.meta.title : tabMenu) }}
|
||||
</el-divider>
|
||||
<template v-for="item in partialRoutes" :key="item.path">
|
||||
<vab-menu v-if="!item.meta.hidden" :item="item" />
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'sass:math';
|
||||
@mixin active {
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background-color: $base-column-second-menu-active !important;
|
||||
|
||||
i,
|
||||
svg {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary);
|
||||
background-color: $base-column-second-menu-active !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-bar-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: var(--el-left-menu-width);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: $base-column-second-menu-background;
|
||||
box-shadow: $base-box-shadow;
|
||||
|
||||
&-vertical,
|
||||
&-card,
|
||||
&-arrow {
|
||||
:deep() {
|
||||
.el-tabs + .el-menu {
|
||||
left: $base-left-menu-width-min;
|
||||
width: calc(
|
||||
var(--el-left-menu-width) - #{$base-left-menu-width-min}
|
||||
);
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-horizontal {
|
||||
:deep() {
|
||||
.logo-container-column {
|
||||
.logo {
|
||||
width: $base-left-menu-width-min * 1.3 !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: $base-left-menu-width-min * 1.3 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs + .el-menu {
|
||||
left: $base-left-menu-width-min * 1.3;
|
||||
width: calc(
|
||||
var(--el-left-menu-width) -
|
||||
#{$base-left-menu-width-min} * 1.3
|
||||
);
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-card {
|
||||
:deep() {
|
||||
.el-tabs {
|
||||
.el-tabs__item {
|
||||
padding: 5px !important;
|
||||
|
||||
.vab-column-grid {
|
||||
width: $base-left-menu-width-min - 10 !important;
|
||||
height: $base-left-menu-width-min - 10 !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: transparent !important;
|
||||
|
||||
.vab-column-grid {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs + .el-menu {
|
||||
left: $base-left-menu-width-min + 10;
|
||||
width: calc(
|
||||
var(--el-left-menu-width) -
|
||||
#{$base-left-menu-width-min} - 20px
|
||||
);
|
||||
}
|
||||
|
||||
.el-sub-menu .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
min-width: 180px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
:deep() {
|
||||
.el-tabs {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
background: transparent !important;
|
||||
|
||||
.vab-column-grid {
|
||||
background: transparent !important;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
content: '';
|
||||
border-color: transparent
|
||||
#{var(--el-color-white)} transparent
|
||||
transparent;
|
||||
border-style: solid dashed dashed;
|
||||
border-width: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs + .el-menu {
|
||||
left: $base-left-menu-width-min + 10;
|
||||
width: calc(
|
||||
var(--el-left-menu-width) -
|
||||
#{$base-left-menu-width-min} - 20px
|
||||
);
|
||||
}
|
||||
|
||||
.el-sub-menu .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
min-width: 180px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: $base-left-menu-width-min;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
|
||||
&-vertical,
|
||||
&-card,
|
||||
&-arrow {
|
||||
justify-content: center;
|
||||
height: $base-left-menu-width-min;
|
||||
|
||||
> div {
|
||||
svg {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: $base-font-size-default + 2;
|
||||
height: $base-font-size-default + 2;
|
||||
margin: auto !important;
|
||||
}
|
||||
|
||||
[class*='ri-'] {
|
||||
display: block;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-horizontal {
|
||||
justify-content: left;
|
||||
width: $base-left-menu-width-min * 1.3;
|
||||
height: #{math.div($base-left-menu-width-min, 1.3)};
|
||||
padding-left: #{math.div($base-padding, 2)};
|
||||
}
|
||||
}
|
||||
|
||||
:deep() {
|
||||
* {
|
||||
transition: $base-transition;
|
||||
}
|
||||
|
||||
.el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.el-tabs {
|
||||
position: fixed;
|
||||
box-shadow: $base-box-shadow;
|
||||
|
||||
.el-tabs__header.is-left {
|
||||
margin-right: 0 !important;
|
||||
|
||||
.el-tabs__nav-wrap.is-left {
|
||||
margin-right: 0 !important;
|
||||
background: $base-column-first-menu-background;
|
||||
|
||||
.el-tabs__nav-scroll {
|
||||
height: calc(
|
||||
100vh - #{$base-logo-height}
|
||||
) !important;
|
||||
overflow-y: auto !important;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
height: auto !important;
|
||||
overflow: hidden !important;
|
||||
background: $base-column-first-menu-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
color: var(--el-color-white);
|
||||
|
||||
&.is-active {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-prev,
|
||||
.el-tabs__active-bar.is-left,
|
||||
.el-tabs--left .el-tabs__nav-wrap.is-left::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-tabs--left .el-tabs__nav-wrap.is-left.is-scrollable {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border: 0;
|
||||
|
||||
.el-divider {
|
||||
margin: 0 0 #{$base-margin} 0;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
&__text {
|
||||
color: var(--el-color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: $base-menu-item-height;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: $base-menu-item-height;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-collapse {
|
||||
:deep() {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
104
library/components/VabErrorLog/index.vue
Normal file
104
library/components/VabErrorLog/index.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts" setup>
|
||||
import { useErrorLogStore } from '@/store/modules/errorLog'
|
||||
|
||||
const errorLogStore = useErrorLogStore()
|
||||
const { errorLogs } = storeToRefs(errorLogStore)
|
||||
const { clearErrorLog } = errorLogStore
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
})
|
||||
const searchList = [
|
||||
{
|
||||
title: '百度搜索',
|
||||
url: 'https://www.baidu.com/baidu?wd=',
|
||||
icon: 'baidu-line',
|
||||
},
|
||||
{
|
||||
title: '谷歌搜索',
|
||||
url: 'https://www.google.com/search?q=',
|
||||
icon: 'google-line',
|
||||
},
|
||||
]
|
||||
|
||||
const clearAll = () => {
|
||||
state.dialogVisible = false
|
||||
clearErrorLog()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="errorLogs.length > 0">
|
||||
<el-badge
|
||||
type="danger"
|
||||
:value="errorLogs.length"
|
||||
@click="state.dialogVisible = true"
|
||||
>
|
||||
<vab-icon icon="bug-line" />
|
||||
</el-badge>
|
||||
|
||||
<el-dialog
|
||||
v-model="state.dialogVisible"
|
||||
append-to-body
|
||||
title="admin-plus 异常捕获(温馨提示:错误必须解决)"
|
||||
width="70%"
|
||||
>
|
||||
<el-table border :data="errorLogs">
|
||||
<el-table-column label="报错路由">
|
||||
<template #default="{ row }">
|
||||
<a :href="row.url" target="_blank">
|
||||
<el-tag type="success">{{ row.url }}</el-tag>
|
||||
</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="错误信息">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="danger">{{ row.err.message }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="错误详情" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-popover placement="top-start" trigger="hover">
|
||||
{{ row.err.stack }}
|
||||
<template #reference>
|
||||
<el-button>查看</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="380">
|
||||
<template #default="{ row }">
|
||||
<a
|
||||
v-for="(item, index) in searchList"
|
||||
:key="index"
|
||||
:href="item.url + row.err.message"
|
||||
target="_blank"
|
||||
>
|
||||
<el-button>
|
||||
<vab-icon :icon="item.icon" />
|
||||
{{ item.title }}
|
||||
</el-button>
|
||||
</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="state.dialogVisible = false">
|
||||
取 消
|
||||
</el-button>
|
||||
<el-button type="danger" @click="clearAll">暂不显示</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-badge) {
|
||||
.el-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
22
library/components/VabFold/index.vue
Normal file
22
library/components/VabFold/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { collapse } = storeToRefs(settingsStore)
|
||||
const { toggleCollapse } = settingsStore
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vab-icon
|
||||
class="fold-unfold"
|
||||
:icon="collapse ? 'menu-unfold-line' : 'menu-fold-line'"
|
||||
@click="toggleCollapse"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fold-unfold {
|
||||
color: var(--el-color-grey);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
47
library/components/VabFooter/index.vue
Normal file
47
library/components/VabFooter/index.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const fullYear = new Date().getFullYear()
|
||||
const settingsStore = useSettingsStore()
|
||||
const { title } = storeToRefs(settingsStore)
|
||||
|
||||
// 国家法律法规要求显示备案号 实际项目请自行修改为自己的备案信息及域名
|
||||
// const beianShow = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="vab-footer">
|
||||
Copyright
|
||||
<vab-icon icon="copyright-line" />
|
||||
{{ fullYear }} {{ title }}
|
||||
<!-- <a
|
||||
v-if="beianShow"
|
||||
class="beian"
|
||||
href="https://beian.miit.gov.cn/#/Integrated/index"
|
||||
target="_blank"
|
||||
>
|
||||
鲁ICP备2021002317号-1
|
||||
</a> -->
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 55px;
|
||||
padding: 0 #{$base-padding} 0 #{$base-padding};
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
background: var(--el-color-white);
|
||||
border-top: 1px dashed #{$base-border-color};
|
||||
|
||||
i {
|
||||
margin: 0 5px;
|
||||
}
|
||||
.beian {
|
||||
margin-left: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
16
library/components/VabFullScreen/index.vue
Normal file
16
library/components/VabFullScreen/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vab-icon
|
||||
v-if="theme.showFullScreen"
|
||||
:icon="isFullscreen ? 'fullscreen-exit-fill' : 'fullscreen-fill'"
|
||||
@click="toggle"
|
||||
/>
|
||||
</template>
|
||||
233
library/components/VabHeader/index.vue
Normal file
233
library/components/VabHeader/index.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<script lang="ts" setup>
|
||||
import variables from '@vab/styles/variables/variables.module.scss'
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
|
||||
defineProps({
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'horizontal',
|
||||
},
|
||||
})
|
||||
|
||||
const routesStore = useRoutesStore()
|
||||
const { getActiveMenu: activeMenu, getRoutes: routes } =
|
||||
storeToRefs(routesStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vab-header">
|
||||
<div class="vab-main">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<vab-logo />
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div class="right-panel">
|
||||
<el-menu
|
||||
v-if="'horizontal' === layout"
|
||||
:active-text-color="variables['menu-color-active']"
|
||||
:background-color="variables['menu-background']"
|
||||
:default-active="activeMenu.data"
|
||||
menu-trigger="hover"
|
||||
mode="horizontal"
|
||||
style="width: 100%"
|
||||
:text-color="variables['menu-color']"
|
||||
>
|
||||
<template
|
||||
v-for="(item, index) in routes.flatMap(
|
||||
(route) =>
|
||||
route['meta'] &&
|
||||
route['meta']['levelHidden'] &&
|
||||
route['children']
|
||||
? [...route['children']]
|
||||
: route
|
||||
)"
|
||||
>
|
||||
<vab-menu
|
||||
v-if="
|
||||
item['meta'] && !item['meta']['hidden']
|
||||
"
|
||||
:key="index + item['name']"
|
||||
:item="item"
|
||||
:layout="layout"
|
||||
/>
|
||||
</template>
|
||||
</el-menu>
|
||||
<vab-error-log />
|
||||
<vab-lock />
|
||||
<vab-search />
|
||||
<vab-notice />
|
||||
<vab-full-screen />
|
||||
<vab-language />
|
||||
<vab-theme />
|
||||
<vab-refresh />
|
||||
<vab-avatar />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'sass:math';
|
||||
|
||||
$base-menu-height: 40px;
|
||||
.vab-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: flex-end;
|
||||
height: $base-header-height;
|
||||
background: $base-menu-background;
|
||||
|
||||
.vab-main {
|
||||
padding: 0 #{$base-padding} 0 #{$base-padding};
|
||||
|
||||
.right-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
height: $base-header-height;
|
||||
|
||||
:deep() {
|
||||
.el-sub-menu__icon-more {
|
||||
margin-top: #{math.div($base-menu-height - 20, 2)} !important;
|
||||
margin-right: 20px !important;
|
||||
}
|
||||
|
||||
> .el-menu--horizontal.el-menu {
|
||||
> .el-sub-menu > .el-sub-menu__title {
|
||||
padding-right: 0;
|
||||
|
||||
> .el-sub-menu__icon-arrow {
|
||||
position: relative !important;
|
||||
margin-top: -5px !important;
|
||||
margin-right: 0;
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
> .el-menu-item {
|
||||
.el-tag {
|
||||
position: relative !important;
|
||||
margin-top: 0 !important;
|
||||
margin-right: -20px;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.vab-dot {
|
||||
float: right;
|
||||
margin-top: #{math.div(
|
||||
$base-header-height - 6,
|
||||
2
|
||||
)} +
|
||||
1;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1199px) {
|
||||
.el-tag {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border: 0 !important;
|
||||
* {
|
||||
border: 0 !important;
|
||||
}
|
||||
&.el-menu--horizontal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
height: $base-menu-height;
|
||||
border: 0 !important;
|
||||
|
||||
> .el-menu-item,
|
||||
> .el-sub-menu {
|
||||
height: $base-menu-height;
|
||||
margin-right: 3px;
|
||||
line-height: $base-menu-height;
|
||||
border-radius: 3px;
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
float: right;
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
> .el-sub-menu__title {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
height: $base-menu-height;
|
||||
line-height: $base-menu-height;
|
||||
border: 0 !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[class*='ri-'],
|
||||
.vab-icon {
|
||||
margin-left: 0;
|
||||
color: var(--el-color-white);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
.vab-icon {
|
||||
margin-top: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu,
|
||||
.el-menu-item {
|
||||
i,
|
||||
.vab-icon {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border: 0 !important;
|
||||
|
||||
.el-sub-menu__title {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
&.is-active {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
|
||||
.user-name + i {
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
|
||||
[class*='ri-'] {
|
||||
margin-left: $base-margin;
|
||||
color: var(--el-color-white);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
svg {
|
||||
margin-right: 0;
|
||||
color: var(--el-color-white);
|
||||
cursor: pointer;
|
||||
fill: var(--el-color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
library/components/VabLanguage/index.vue
Normal file
30
library/components/VabLanguage/index.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import getPageTitle from '@/utils/pageTitle'
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
const { changeLanguage } = settingsStore
|
||||
|
||||
const handleCommand = (language: string) => {
|
||||
changeLanguage(language)
|
||||
locale.value = language
|
||||
document.title = getPageTitle(route.meta.title)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dropdown v-if="theme.showLanguage" @command="handleCommand">
|
||||
<vab-icon icon="translate" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="zh">中文简体</el-dropdown-item>
|
||||
<el-dropdown-item command="en">English</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
27
library/components/VabLink/index.vue
Normal file
27
library/components/VabLink/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts" setup>
|
||||
import { isExternal } from '@/utils/validate'
|
||||
|
||||
const props = defineProps({
|
||||
to: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const type = computed(() => (isExternal(props.to) ? 'a' : 'router-link'))
|
||||
|
||||
const linkProps = () =>
|
||||
isExternal(props.to)
|
||||
? {
|
||||
href: props.to,
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
}
|
||||
: { to: props.to }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="type" v-bind="linkProps()">
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
||||
225
library/components/VabLock/index.vue
Normal file
225
library/components/VabLock/index.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import { translate } from '@/i18n'
|
||||
|
||||
const vFocus: any = {
|
||||
mounted(el: HTMLElement) {
|
||||
el.querySelector('input')?.focus()
|
||||
},
|
||||
}
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { avatar } = storeToRefs(userStore)
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme, lock, title } = storeToRefs(settingsStore)
|
||||
const { handleLock: _handleLock, handleUnLock: _handleUnLock } =
|
||||
settingsStore
|
||||
|
||||
const background = ref(
|
||||
`https://gcore.jsdelivr.net/gh/zxwk1998/image/vab-image-lock/${Math.round(Math.random() * 31)}.jpg`
|
||||
)
|
||||
const randomBackground = () => {
|
||||
background.value = `https://gcore.jsdelivr.net/gh/zxwk1998/image/vab-image-lock/${Math.round(Math.random() * 31)}.jpg`
|
||||
}
|
||||
|
||||
const validatePass = (rule: any, value: string, callback: any) => {
|
||||
if (value === '' || value !== '123456') {
|
||||
callback(new Error('请输入正确的密码'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const formRef = ref()
|
||||
const form = ref({
|
||||
password: '123456',
|
||||
})
|
||||
const rules = {
|
||||
password: [{ validator: validatePass, trigger: 'blur' }],
|
||||
}
|
||||
|
||||
let lockIcon = true
|
||||
const handleUnLock = () => {
|
||||
formRef.value.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
lockIcon = false
|
||||
setTimeout(async () => {
|
||||
await _handleUnLock()
|
||||
lockIcon = true
|
||||
await randomBackground()
|
||||
const el = document.querySelector(
|
||||
'.vab-side-bar'
|
||||
) as HTMLElement
|
||||
if (el) el.removeAttribute('style')
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleLock = () => {
|
||||
_handleLock()
|
||||
const el = document.querySelector('.vab-side-bar') as HTMLElement
|
||||
if (el) el.style.display = 'none'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vab-icon v-if="theme.showLock" icon="lock-line" @click="handleLock" />
|
||||
<transition v-if="theme.showLock" mode="out-in" name="fade-transform">
|
||||
<div v-if="lock" class="vab-screen-lock">
|
||||
<div
|
||||
class="vab-screen-lock-background"
|
||||
:style="{
|
||||
background: `fixed url(${background}) center`,
|
||||
backgroundSize: '100% 100%',
|
||||
filter: 'blur(10px)',
|
||||
}"
|
||||
></div>
|
||||
|
||||
<div class="vab-screen-lock-content">
|
||||
<div class="vab-screen-lock-content-title">
|
||||
<el-avatar :size="180" :src="avatar" />
|
||||
<vab-icon
|
||||
:icon="lockIcon ? 'lock-line' : 'lock-unlock-line'"
|
||||
/>
|
||||
{{ title }} {{ translate('屏幕已锁定') }}
|
||||
</div>
|
||||
<div class="vab-screen-lock-content-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="" :label-width="0" prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
v-focus
|
||||
autocomplete="off"
|
||||
placeholder="请输出密码123456"
|
||||
type="password"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-button
|
||||
native-type="submit"
|
||||
type="primary"
|
||||
@click="handleUnLock"
|
||||
>
|
||||
<vab-icon
|
||||
:icon="
|
||||
lockIcon
|
||||
? 'lock-line'
|
||||
: 'lock-unlock-line'
|
||||
"
|
||||
/>
|
||||
{{ translate('解锁') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<span @click="randomBackground">
|
||||
{{ translate('切换壁纸') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-screen-lock {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: $base-z-index;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
backdrop-filter: blur(10px);
|
||||
transition: $base-transition;
|
||||
|
||||
&-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: $base-z-index - 1;
|
||||
}
|
||||
|
||||
&-content {
|
||||
z-index: $base-z-index;
|
||||
padding: 40px 95px 40px 95px;
|
||||
color: #252a30;
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 15px;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
> span {
|
||||
font-size: $base-font-size-small;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-title {
|
||||
line-height: 50px;
|
||||
color: #252a30;
|
||||
text-align: center;
|
||||
|
||||
.ri-lock-line,
|
||||
.ri-lock-unlock-line {
|
||||
display: block;
|
||||
margin: auto !important;
|
||||
font-size: 30px;
|
||||
color: #252a30 !important;
|
||||
transition: $base-transition;
|
||||
}
|
||||
}
|
||||
|
||||
&-form {
|
||||
:deep() {
|
||||
.el-input__inner {
|
||||
width: 280px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
padding-right: 0;
|
||||
.el-input__suffix {
|
||||
right: 0;
|
||||
|
||||
.el-button {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
i {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__validateIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.vab-screen-lock-content {
|
||||
width: auto !important;
|
||||
margin: 5vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
123
library/components/VabLogo/index.vue
Normal file
123
library/components/VabLogo/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme, logo, title } = storeToRefs(settingsStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="logo-container"
|
||||
:class="{
|
||||
['logo-container-' + theme.layout]: true,
|
||||
}"
|
||||
>
|
||||
<router-link to="/">
|
||||
<span class="logo">
|
||||
<!-- 使用自定义svg示例 -->
|
||||
<vab-icon v-if="logo" :icon="logo" is-custom-svg />
|
||||
</span>
|
||||
<span
|
||||
class="title"
|
||||
:class="{ 'hidden-xs-only': theme.layout === 'horizontal' }"
|
||||
>
|
||||
{{ title }}
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin container {
|
||||
position: relative;
|
||||
height: $base-header-height;
|
||||
overflow: hidden;
|
||||
line-height: $base-header-height;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@mixin logo {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
color: $base-title-color;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
@mixin title {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 20px;
|
||||
line-height: 55px;
|
||||
vertical-align: middle;
|
||||
color: $base-title-color;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
&-horizontal,
|
||||
&-common {
|
||||
@include container;
|
||||
|
||||
.logo {
|
||||
svg,
|
||||
img {
|
||||
@include logo;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@include title;
|
||||
}
|
||||
}
|
||||
|
||||
&-vertical,
|
||||
&-column,
|
||||
&-comprehensive,
|
||||
&-float {
|
||||
@include container;
|
||||
|
||||
height: $base-logo-height;
|
||||
line-height: $base-logo-height;
|
||||
text-align: center;
|
||||
|
||||
.logo {
|
||||
svg,
|
||||
img {
|
||||
@include logo;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@include title;
|
||||
max-width: calc(var(--el-left-menu-width) - 60px);
|
||||
}
|
||||
}
|
||||
|
||||
&-column {
|
||||
background: $base-column-second-menu-background !important;
|
||||
|
||||
.logo {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
display: block;
|
||||
width: $base-left-menu-width-min;
|
||||
height: $base-logo-height;
|
||||
margin: 0;
|
||||
background: $base-column-first-menu-background;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-left: $base-left-menu-width-min !important;
|
||||
color: var(--el-color-black) !important;
|
||||
background: $base-column-second-menu-background !important;
|
||||
@include title;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
82
library/components/VabMenu/components/VabMenuItem.vue
Normal file
82
library/components/VabMenu/components/VabMenuItem.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import { translate } from '@/i18n'
|
||||
import { isHashRouterMode } from '@/config'
|
||||
|
||||
const props = defineProps({
|
||||
itemOrMenu: {
|
||||
type: Object,
|
||||
default() {
|
||||
return null
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const $pub: any = inject('$pub')
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { device } = storeToRefs(settingsStore)
|
||||
const { foldSideBar } = settingsStore
|
||||
|
||||
const handleLink = () => {
|
||||
const routePath = props.itemOrMenu.path
|
||||
const target = props.itemOrMenu.meta.target
|
||||
if (target === '_blank') {
|
||||
if (isExternal(routePath)) window.open(routePath)
|
||||
else if (route.path !== routePath)
|
||||
isHashRouterMode
|
||||
? window.open(`/#${routePath}`)
|
||||
: window.open(routePath)
|
||||
} else {
|
||||
if (isExternal(routePath)) window.location.href = routePath
|
||||
else if (route.path !== routePath) {
|
||||
if (device.value === 'mobile') foldSideBar()
|
||||
router.push(props.itemOrMenu.path)
|
||||
} else $pub('reload-router-view')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu-item :index="itemOrMenu.path" @click="handleLink">
|
||||
<vab-icon
|
||||
v-if="itemOrMenu.meta.icon"
|
||||
:icon="itemOrMenu.meta.icon"
|
||||
:is-custom-svg="itemOrMenu.meta.isCustomSvg"
|
||||
:title="translate(itemOrMenu.meta.title)"
|
||||
/>
|
||||
<span :title="translate(itemOrMenu.meta.title)">
|
||||
{{ translate(itemOrMenu.meta.title) }}
|
||||
</span>
|
||||
<el-tag v-if="itemOrMenu.meta.badge" effect="dark" type="danger">
|
||||
{{ itemOrMenu.meta.badge }}
|
||||
</el-tag>
|
||||
<span v-if="itemOrMenu.meta.dot" class="vab-dot vab-dot-error">
|
||||
<span />
|
||||
</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'sass:math';
|
||||
:deep(.el-tag) {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
height: 16px;
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
// margin-top: #{math.div($base-menu-item-height - 16, 2)};
|
||||
line-height: 16px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.vab-dot {
|
||||
position: absolute !important;
|
||||
right: 20px;
|
||||
// margin-top: #{math.div($base-menu-item-height - 6, 2)};
|
||||
}
|
||||
</style>
|
||||
36
library/components/VabMenu/components/VabSubMenu.vue
Normal file
36
library/components/VabMenu/components/VabSubMenu.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import { translate } from '@/i18n'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
defineProps({
|
||||
itemOrMenu: {
|
||||
type: Object,
|
||||
default() {
|
||||
return null
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const settingsStore: any = useSettingsStore()
|
||||
const { theme }: any = storeToRefs(settingsStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-sub-menu
|
||||
:index="itemOrMenu.path"
|
||||
:teleported="theme.layout != 'horizontal'"
|
||||
>
|
||||
<template #title>
|
||||
<vab-icon
|
||||
v-if="itemOrMenu.meta.icon"
|
||||
:icon="itemOrMenu.meta.icon"
|
||||
:is-custom-svg="itemOrMenu.meta.isCustomSvg"
|
||||
:title="translate(itemOrMenu.meta.title)"
|
||||
/>
|
||||
<span :title="translate(itemOrMenu.meta.title)">
|
||||
{{ translate(itemOrMenu.meta.title) }}
|
||||
</span>
|
||||
</template>
|
||||
<slot />
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
83
library/components/VabMenu/index.vue
Normal file
83
library/components/VabMenu/index.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<component
|
||||
:is="menuComponent"
|
||||
v-if="!item.meta.hidden"
|
||||
:item-or-menu="item"
|
||||
>
|
||||
<template v-if="item.children && item.children.length">
|
||||
<vab-menu
|
||||
v-for="route in item.children"
|
||||
:key="route.path"
|
||||
:item="route"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/* 防止偶发性自动导入失败 */
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const imports = require.context('./components', true, /\.vue$/)
|
||||
const Components: {
|
||||
[key: string]: any
|
||||
} = {}
|
||||
imports.keys().forEach((key) => {
|
||||
Components[key.replace(/(\/|\.|vue)/g, '')] = imports(key).default
|
||||
})
|
||||
|
||||
export default defineComponent({
|
||||
name: 'VabMenu',
|
||||
components: Components,
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const settingsStore = useSettingsStore()
|
||||
const { collapse } = storeToRefs(settingsStore)
|
||||
|
||||
const menuComponent = computed(() =>
|
||||
props.item.children &&
|
||||
props.item.children.some((_route: any) => {
|
||||
return _route.meta.hidden !== true
|
||||
})
|
||||
? 'VabSubMenu'
|
||||
: 'VabMenuItem'
|
||||
)
|
||||
|
||||
return {
|
||||
collapse,
|
||||
menuComponent,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-menu-children-height {
|
||||
height: 60vh !important;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--由于element-plus
|
||||
bug使用teleported=false会导致多级路由无法显示,故所有菜单必须生成至body下,样式必须放到body下-->
|
||||
<style lang="scss">
|
||||
.el-popper.is-light {
|
||||
border: 0 !important;
|
||||
}
|
||||
</style>
|
||||
165
library/components/VabNav/index.vue
Normal file
165
library/components/VabNav/index.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
import { translate } from '@/i18n'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import { openFirstMenu } from '@/config'
|
||||
|
||||
defineProps({
|
||||
layout: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const routesStore: any = useRoutesStore()
|
||||
const {
|
||||
getTab: tab,
|
||||
getTabMenu: tabMenu,
|
||||
getRoutes: routes,
|
||||
} = storeToRefs(routesStore)
|
||||
|
||||
const handleTabClick = () => {
|
||||
nextTick(() => {
|
||||
if (isExternal(tabMenu.value.path)) {
|
||||
window.open(tabMenu.value.path)
|
||||
setTimeout(() => {
|
||||
router.push('/')
|
||||
}, 1000)
|
||||
} else if (openFirstMenu)
|
||||
router.push(tabMenu.value.redirect || tabMenu.value)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vab-nav">
|
||||
<el-row :gutter="15">
|
||||
<el-col :lg="12" :md="12" :sm="12" :xl="12" :xs="4">
|
||||
<div class="left-panel">
|
||||
<vab-fold v-if="layout !== 'float'" />
|
||||
<el-tabs
|
||||
v-if="layout === 'comprehensive'"
|
||||
v-model="tab.data"
|
||||
tab-position="top"
|
||||
@tab-click="handleTabClick"
|
||||
>
|
||||
<template
|
||||
v-for="(item, index) in routes"
|
||||
:key="index + item.name"
|
||||
>
|
||||
<el-tab-pane :name="item.name">
|
||||
<template #label>
|
||||
<vab-icon
|
||||
v-if="item.meta.icon"
|
||||
:icon="item.meta.icon"
|
||||
:is-custom-svg="item.meta.isCustomSvg"
|
||||
style="min-width: 16px"
|
||||
/>
|
||||
<span>
|
||||
{{ translate(item.meta.title) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
<vab-breadcrumb v-else class="hidden-xs-only" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :lg="12" :md="12" :sm="12" :xl="12" :xs="20">
|
||||
<div class="right-panel">
|
||||
<vab-error-log />
|
||||
<vab-lock />
|
||||
<vab-search />
|
||||
<vab-notice />
|
||||
<vab-full-screen />
|
||||
<vab-language />
|
||||
<vab-theme />
|
||||
<vab-refresh />
|
||||
<vab-avatar />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-nav {
|
||||
position: relative;
|
||||
height: $base-nav-height;
|
||||
padding-right: $base-padding;
|
||||
padding-left: $base-padding;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
background: var(--el-color-white);
|
||||
box-shadow: $base-box-shadow;
|
||||
|
||||
.left-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
height: $base-nav-height;
|
||||
|
||||
:deep() {
|
||||
.fold-unfold {
|
||||
margin-right: $base-margin;
|
||||
}
|
||||
|
||||
.el-tabs {
|
||||
width: 100%;
|
||||
margin-left: $base-margin;
|
||||
|
||||
.el-tabs__header {
|
||||
margin: 0;
|
||||
|
||||
> .el-tabs__nav-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.el-icon-arrow-left,
|
||||
.el-icon-arrow-right {
|
||||
font-weight: 600;
|
||||
color: var(--el-color-grey);
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
i {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__nav-wrap::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
height: $base-nav-height;
|
||||
|
||||
:deep() {
|
||||
[class*='ri-'] {
|
||||
margin-left: $base-margin;
|
||||
color: var(--el-color-grey);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button {
|
||||
[class*='ri-'] {
|
||||
margin-left: 0;
|
||||
color: var(--el-color-white);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
356
library/components/VabNotice/index.vue
Normal file
356
library/components/VabNotice/index.vue
Normal file
@@ -0,0 +1,356 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import { translate } from '@/i18n'
|
||||
import { getList } from '@/api/notice'
|
||||
|
||||
const $baseMessage: any = inject('$baseMessage')
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
|
||||
const activeName = ref('notice')
|
||||
const notices: any = ref([])
|
||||
const badge = ref(undefined)
|
||||
|
||||
const fetchData = async () => {
|
||||
const {
|
||||
data: { list, total },
|
||||
} = await getList()
|
||||
notices.value = list
|
||||
badge.value = total === 0 ? undefined : total
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
if (theme.value.showNotice) fetchData()
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleClearNotice = () => {
|
||||
badge.value = undefined
|
||||
notices.value = []
|
||||
$baseMessage('清空消息成功', 'success', 'vab-hey-message-success')
|
||||
}
|
||||
|
||||
const formatTime = (time: string) => {
|
||||
const date = new Date(time)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const minutes = Math.floor(diff / 60000)
|
||||
const hours = Math.floor(diff / 3600000)
|
||||
const days = Math.floor(diff / 86400000)
|
||||
|
||||
if (minutes < 1) return '刚刚'
|
||||
if (minutes < 60) return `${minutes}分钟前`
|
||||
if (hours < 24) return `${hours}小时前`
|
||||
if (days < 7) return `${days}天前`
|
||||
return date.toLocaleDateString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-badge
|
||||
v-if="theme.showNotice"
|
||||
class="notice-badge"
|
||||
type="danger"
|
||||
:value="badge"
|
||||
>
|
||||
<el-popover
|
||||
placement="bottom-end"
|
||||
popper-class="notice-popover"
|
||||
:show-arrow="false"
|
||||
:width="380"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="notice-trigger">
|
||||
<vab-icon icon="notification-line" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="notice-container">
|
||||
<div class="notice-header">
|
||||
<h3>{{ translate('消息中心') }}</h3>
|
||||
<el-button
|
||||
class="clear-btn"
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
@click="handleClearNotice"
|
||||
>
|
||||
<vab-icon icon="delete-bin-line" />
|
||||
{{ translate('清空') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
class="notice-tabs"
|
||||
@tab-click="handleClick"
|
||||
>
|
||||
<el-tab-pane :label="translate('通知')" name="notice">
|
||||
<div class="notice-list">
|
||||
<el-scrollbar height="320px">
|
||||
<div
|
||||
v-if="notices.length === 0"
|
||||
class="empty-state"
|
||||
>
|
||||
<vab-icon
|
||||
class="empty-icon"
|
||||
icon="notification-off-line"
|
||||
/>
|
||||
<p>{{ translate('暂无通知') }}</p>
|
||||
</div>
|
||||
<div v-else class="notice-items">
|
||||
<div
|
||||
v-for="(item, index) in notices"
|
||||
:key="index"
|
||||
class="notice-item"
|
||||
>
|
||||
<div class="notice-avatar">
|
||||
<el-avatar
|
||||
:size="40"
|
||||
:src="item.image"
|
||||
/>
|
||||
<div
|
||||
v-if="!item.read"
|
||||
class="notice-status"
|
||||
></div>
|
||||
</div>
|
||||
<div class="notice-content">
|
||||
<div
|
||||
class="notice-text"
|
||||
v-html="item.notice"
|
||||
></div>
|
||||
<div class="notice-time">
|
||||
{{
|
||||
formatTime(
|
||||
item.time || new Date()
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="translate('邮件')" name="email">
|
||||
<div class="notice-list">
|
||||
<el-scrollbar height="320px">
|
||||
<div
|
||||
v-if="notices.length === 0"
|
||||
class="empty-state"
|
||||
>
|
||||
<vab-icon
|
||||
class="empty-icon"
|
||||
icon="mail-line"
|
||||
/>
|
||||
<p>{{ translate('暂无邮件') }}</p>
|
||||
</div>
|
||||
<div v-else class="notice-items">
|
||||
<div
|
||||
v-for="(item, index) in notices"
|
||||
:key="index"
|
||||
class="notice-item"
|
||||
>
|
||||
<div class="notice-avatar">
|
||||
<el-avatar
|
||||
:size="40"
|
||||
:src="item.image"
|
||||
/>
|
||||
<div
|
||||
v-if="!item.read"
|
||||
class="notice-status"
|
||||
></div>
|
||||
</div>
|
||||
<div class="notice-content">
|
||||
<div class="notice-text">
|
||||
{{ item.email }}
|
||||
</div>
|
||||
<div class="notice-time">
|
||||
{{
|
||||
formatTime(
|
||||
item.time || new Date()
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-popover>
|
||||
</el-badge>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notice-badge {
|
||||
:deep(.el-badge__content) {
|
||||
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
|
||||
}
|
||||
}
|
||||
|
||||
.notice-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notice-container {
|
||||
.notice-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px 12px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-danger);
|
||||
background: var(--el-color-danger-light-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-tabs {
|
||||
:deep(.el-tabs__header) {
|
||||
padding: 0 20px;
|
||||
margin: 0;
|
||||
|
||||
.el-tabs__nav-wrap {
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.is-active {
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
min-width: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-list {
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
|
||||
.empty-icon {
|
||||
margin-bottom: 12px;
|
||||
font-size: 48px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-items {
|
||||
padding: 8px 0;
|
||||
|
||||
.notice-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 12px 20px;
|
||||
margin: 0 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.notice-avatar {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12px;
|
||||
|
||||
.notice-status {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--el-color-danger);
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.notice-text {
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--el-text-color-primary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.notice-time {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.notice-popover {
|
||||
padding: 0 !important;
|
||||
border: 1px solid var(--el-border-color-lighter) !important;
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12) !important;
|
||||
|
||||
.el-popover__title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<el-col :span="24">
|
||||
<div class="bottom-panel">
|
||||
<slot />
|
||||
</div>
|
||||
</el-col>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
span: {
|
||||
type: Number,
|
||||
default: 14,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-col :lg="span" :md="24" :sm="24" :xl="span" :xs="24">
|
||||
<div class="left-panel">
|
||||
<slot />
|
||||
</div>
|
||||
</el-col>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
span: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-col :lg="span" :md="24" :sm="24" :xl="span" :xs="24">
|
||||
<div class="right-panel">
|
||||
<slot />
|
||||
</div>
|
||||
</el-col>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<el-col :span="24">
|
||||
<div class="top-panel">
|
||||
<slot />
|
||||
</div>
|
||||
</el-col>
|
||||
</template>
|
||||
60
library/components/VabQueryForm/index.vue
Normal file
60
library/components/VabQueryForm/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-row class="vab-query-form" :gutter="0">
|
||||
<slot />
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'sass:math';
|
||||
@mixin panel {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: $base-input-height;
|
||||
margin: 0 0 #{math.div($base-margin, 2)} 0;
|
||||
.el-form-item__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> .el-button {
|
||||
margin: 0 10px #{math.div($base-margin, 2)} 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-query-form {
|
||||
:deep() {
|
||||
.el-form-item:first-child {
|
||||
margin: 0 0 #{math.div($base-margin, 2)} 0 !important;
|
||||
}
|
||||
|
||||
.el-form-item + .el-form-item {
|
||||
margin: 0 0 #{math.div($base-margin, 2)} 0 !important;
|
||||
|
||||
.el-button {
|
||||
margin: 0 0 0 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.top-panel {
|
||||
@include panel;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
@include panel;
|
||||
border-top: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
@include panel;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
@include panel;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
20
library/components/VabRefresh/index.vue
Normal file
20
library/components/VabRefresh/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const $pub: any = inject('$pub')
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
|
||||
const refreshRoute = () => {
|
||||
$pub('reload-router-view')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vab-icon
|
||||
v-if="theme.showRefresh"
|
||||
icon="refresh-line"
|
||||
@click="refreshRoute"
|
||||
/>
|
||||
</template>
|
||||
80
library/components/VabRouterView/index.vue
Normal file
80
library/components/VabRouterView/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script lang="ts" setup>
|
||||
import VabProgress from 'nprogress'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import { useTabsStore } from '@/store/modules/tabs'
|
||||
import { handleActivePath } from '@/utils/routes'
|
||||
import { keepAliveMaxNum } from '@/config'
|
||||
import { VabRouteRecord } from '/#/router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const $sub: any = inject('$sub')
|
||||
const $unsub: any = inject('$unsub')
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
const tabsStore = useTabsStore()
|
||||
const { getVisitedRoutes: visitedRoutes } = storeToRefs(tabsStore)
|
||||
|
||||
const componentRef = ref()
|
||||
const routerKey = ref()
|
||||
const keepAliveNameList = ref()
|
||||
|
||||
const updateKeepAliveNameList = (refreshRouteName = null) => {
|
||||
keepAliveNameList.value = visitedRoutes.value
|
||||
.filter(
|
||||
(item: VabRouteRecord) =>
|
||||
!item.meta.noKeepAlive && item.name !== refreshRouteName
|
||||
)
|
||||
.flatMap((item: VabRouteRecord) => item.name)
|
||||
}
|
||||
|
||||
// 更新KeepAlive缓存页面
|
||||
watchEffect(() => {
|
||||
routerKey.value = handleActivePath(route, true)
|
||||
updateKeepAliveNameList()
|
||||
})
|
||||
|
||||
// 获取源码地址
|
||||
$sub('get-code', () => {
|
||||
window.open(
|
||||
`https://github.com/zxwk2024/admin-plus/blob/main/${componentRef.value.$options.__source}`
|
||||
)
|
||||
})
|
||||
|
||||
$sub('reload-router-view', (refreshRouteName: any = route.name) => {
|
||||
if (theme.value.showProgressBar) VabProgress.start()
|
||||
const cacheActivePath = routerKey.value
|
||||
routerKey.value = null
|
||||
updateKeepAliveNameList(refreshRouteName)
|
||||
nextTick(() => {
|
||||
routerKey.value = cacheActivePath
|
||||
updateKeepAliveNameList()
|
||||
})
|
||||
setTimeout(() => {
|
||||
if (theme.value.showProgressBar) VabProgress.done()
|
||||
}, 200)
|
||||
})
|
||||
|
||||
// onUnmounted(() => {
|
||||
// $unsub('get-code')
|
||||
// $unsub('reload-router-view')
|
||||
// })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition
|
||||
mode="out-in"
|
||||
:name="theme.showPageTransition ? 'fade-transform' : 'no-transform'"
|
||||
>
|
||||
<keep-alive :include="keepAliveNameList" :max="keepAliveMaxNum">
|
||||
<component
|
||||
:is="Component"
|
||||
:key="routerKey"
|
||||
ref="componentRef"
|
||||
/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</template>
|
||||
517
library/components/VabSearch/index.vue
Normal file
517
library/components/VabSearch/index.vue
Normal file
@@ -0,0 +1,517 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
|
||||
const isMac = /macintosh|mac os x/i.test(navigator.userAgent)
|
||||
const MOD_KEY = isMac ? 'metaKey' : 'ctrlKey'
|
||||
|
||||
const show = ref(false)
|
||||
const search = ref('')
|
||||
const activeIndex = ref(0)
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const routesStore = useRoutesStore()
|
||||
const { getRoutes } = storeToRefs(routesStore)
|
||||
const router = useRouter()
|
||||
|
||||
// 递归平铺所有菜单项
|
||||
function flattenMenus(
|
||||
routes: any[],
|
||||
basePath = '',
|
||||
parentIcon = undefined
|
||||
): any[] {
|
||||
let result: any[] = []
|
||||
routes.forEach((route) => {
|
||||
if (route.meta && route.meta.hidden) return
|
||||
const fullPath = route.path.startsWith('/')
|
||||
? route.path
|
||||
: `${basePath}/${route.path}`
|
||||
const icon =
|
||||
route.meta && route.meta.icon ? route.meta.icon : parentIcon
|
||||
if (route.meta && route.meta.title) {
|
||||
result.push({
|
||||
title: route.meta.title,
|
||||
path: fullPath,
|
||||
icon,
|
||||
...route.meta,
|
||||
})
|
||||
}
|
||||
if (route.children && route.children.length > 0) {
|
||||
result = result.concat(
|
||||
flattenMenus(route.children, fullPath, icon)
|
||||
)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
const menuList = computed(() => flattenMenus(getRoutes.value))
|
||||
|
||||
// 搜索历史(localStorage)
|
||||
const HISTORY_KEY = 'vab_search_history'
|
||||
const searchHistory = ref<string[]>([])
|
||||
function loadHistory() {
|
||||
const raw = localStorage.getItem(HISTORY_KEY)
|
||||
searchHistory.value = raw ? JSON.parse(raw) : []
|
||||
}
|
||||
function saveHistory(keyword: string) {
|
||||
if (!keyword) return
|
||||
let arr = searchHistory.value.filter((item) => item !== keyword)
|
||||
arr.unshift(keyword)
|
||||
if (arr.length > 8) arr = arr.slice(0, 8)
|
||||
searchHistory.value = arr
|
||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(arr))
|
||||
}
|
||||
function removeHistory(keyword: string) {
|
||||
searchHistory.value = searchHistory.value.filter(
|
||||
(item) => item !== keyword
|
||||
)
|
||||
localStorage.setItem(HISTORY_KEY, JSON.stringify(searchHistory.value))
|
||||
}
|
||||
function clearHistory() {
|
||||
searchHistory.value = []
|
||||
localStorage.removeItem(HISTORY_KEY)
|
||||
}
|
||||
|
||||
// 简单模糊搜索(可扩展为拼音/多字段)
|
||||
const filteredMenus = computed(() => {
|
||||
if (!search.value) return menuList.value
|
||||
return menuList.value.filter(
|
||||
(item) =>
|
||||
item.title.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
(item.path &&
|
||||
item.path
|
||||
.toLowerCase()
|
||||
.includes(search.value.toLowerCase()))
|
||||
)
|
||||
})
|
||||
|
||||
function openSearch() {
|
||||
show.value = true
|
||||
search.value = ''
|
||||
activeIndex.value = 0
|
||||
loadHistory()
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
function closeSearch() {
|
||||
show.value = false
|
||||
}
|
||||
function selectMenu(index: number) {
|
||||
const item = filteredMenus.value[index]
|
||||
if (item && item.path) {
|
||||
saveHistory(search.value)
|
||||
closeSearch()
|
||||
|
||||
// 检查是否为外链
|
||||
if (
|
||||
item.target === '_blank' ||
|
||||
item.path.startsWith('http://') ||
|
||||
item.path.startsWith('https://') ||
|
||||
item.path.startsWith('//')
|
||||
) {
|
||||
// 外链使用window.open
|
||||
window.open(item.path, '_blank')
|
||||
} else {
|
||||
// 内部路由使用router.push
|
||||
router.push(item.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
function selectHistory(keyword: string) {
|
||||
search.value = keyword
|
||||
activeIndex.value = 0
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (show.value) {
|
||||
if (e.key === 'Escape') {
|
||||
closeSearch()
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
activeIndex.value =
|
||||
(activeIndex.value + 1) % filteredMenus.value.length
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault()
|
||||
activeIndex.value =
|
||||
(activeIndex.value - 1 + filteredMenus.value.length) %
|
||||
filteredMenus.value.length
|
||||
} else if (e.key === 'Enter') {
|
||||
selectMenu(activeIndex.value)
|
||||
}
|
||||
} else {
|
||||
if ((e as any)[MOD_KEY] && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault()
|
||||
openSearch()
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', onKeydown)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', onKeydown)
|
||||
})
|
||||
|
||||
function onItemClick(idx: number) {
|
||||
selectMenu(idx)
|
||||
}
|
||||
function onInputKeydown(e: KeyboardEvent) {
|
||||
if (
|
||||
e.key === 'ArrowDown' ||
|
||||
e.key === 'ArrowUp' ||
|
||||
e.key === 'Enter' ||
|
||||
e.key === 'Escape'
|
||||
) {
|
||||
// 交由全局keydown处理
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="vab-search-trigger hidden-xs-only">
|
||||
<vab-icon
|
||||
icon="search-line"
|
||||
title="菜单搜索 ({{ isMac ? '⌘+K' : 'Ctrl+K' }})"
|
||||
@click="openSearch"
|
||||
/>
|
||||
</span>
|
||||
<teleport to="body">
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="show"
|
||||
class="vab-spotlight-mask"
|
||||
@click.self="closeSearch"
|
||||
>
|
||||
<div class="vab-spotlight-card">
|
||||
<div class="vab-spotlight-input-wrap">
|
||||
<vab-icon
|
||||
class="vab-spotlight-input-icon"
|
||||
icon="search-line"
|
||||
/>
|
||||
<input
|
||||
ref="inputRef"
|
||||
v-model="search"
|
||||
autocomplete="off"
|
||||
class="vab-spotlight-input"
|
||||
:placeholder="
|
||||
isMac ? '搜索菜单… (⌘+K)' : '搜索菜单… (Ctrl+K)'
|
||||
"
|
||||
@keydown="onInputKeydown"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="searchHistory.length"
|
||||
class="vab-spotlight-history-wrap"
|
||||
>
|
||||
<div class="vab-spotlight-history">
|
||||
<div class="vab-spotlight-history-title">
|
||||
搜索历史
|
||||
<span
|
||||
class="vab-spotlight-history-clear"
|
||||
@click="clearHistory"
|
||||
>
|
||||
清空
|
||||
</span>
|
||||
</div>
|
||||
<div class="vab-spotlight-history-list">
|
||||
<span
|
||||
v-for="item in searchHistory"
|
||||
:key="item"
|
||||
class="vab-spotlight-history-item"
|
||||
>
|
||||
<span @click="selectHistory(item)">
|
||||
{{ item }}
|
||||
</span>
|
||||
<vab-icon
|
||||
class="vab-spotlight-history-del"
|
||||
icon="close-circle-fill"
|
||||
@click.stop="removeHistory(item)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="vab-spotlight-list">
|
||||
<li
|
||||
v-for="(item, idx) in filteredMenus"
|
||||
:key="item.path + item.title"
|
||||
:class="[
|
||||
'vab-spotlight-item',
|
||||
{ active: idx === activeIndex },
|
||||
]"
|
||||
@mousedown.prevent="onItemClick(idx)"
|
||||
@mouseenter="activeIndex = idx"
|
||||
>
|
||||
<vab-icon
|
||||
v-if="item.icon"
|
||||
class="vab-spotlight-item-icon"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
<div class="vab-spotlight-item-content">
|
||||
<div class="vab-spotlight-item-title">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="vab-spotlight-item-path">
|
||||
{{ item.path }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
v-if="filteredMenus.length === 0"
|
||||
class="vab-spotlight-empty"
|
||||
>
|
||||
<div v-if="!search">请输入关键词进行搜索</div>
|
||||
<div v-else>无匹配菜单</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<button
|
||||
class="vab-spotlight-fab"
|
||||
title="菜单搜索 ({{ isMac ? '⌘+K' : 'Ctrl+K' }})"
|
||||
@click="openSearch"
|
||||
>
|
||||
<vab-icon icon="search-line" />
|
||||
</button>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.18s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.vab-spotlight-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(30, 34, 45, 0.25);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
.vab-spotlight-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 600px;
|
||||
max-width: 96vw;
|
||||
padding: 0 0 8px 0;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.18);
|
||||
backdrop-filter: blur(12px) saturate(120%);
|
||||
animation: fadeInUp 0.18s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(40px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
.vab-spotlight-input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24px 28px 12px 28px;
|
||||
}
|
||||
.vab-spotlight-input-icon {
|
||||
margin-right: 10px;
|
||||
font-size: 22px;
|
||||
color: #666;
|
||||
}
|
||||
.vab-spotlight-input {
|
||||
flex: 1;
|
||||
padding: 8px 0;
|
||||
font-size: 20px;
|
||||
color: #222;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
.vab-spotlight-list {
|
||||
max-height: 340px;
|
||||
padding: 0 8px 0 8px;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
}
|
||||
.vab-spotlight-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 18px;
|
||||
margin-bottom: 2px;
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
transition: background 0.18s;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&.active,
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
.vab-spotlight-item-icon {
|
||||
margin-right: 14px;
|
||||
font-size: 26px !important;
|
||||
color: #666;
|
||||
}
|
||||
.vab-spotlight-item-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.vab-spotlight-item-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.vab-spotlight-item-path {
|
||||
margin-top: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.vab-spotlight-empty {
|
||||
padding: 32px 0 24px 0;
|
||||
font-size: 15px;
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
}
|
||||
.vab-spotlight-fab {
|
||||
position: fixed;
|
||||
right: 32px;
|
||||
bottom: 32px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background: #666;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 16px 0 rgba(102, 102, 102, 0.18);
|
||||
transition:
|
||||
background 0.18s,
|
||||
box-shadow 0.18s,
|
||||
transform 0.18s;
|
||||
&:hover {
|
||||
background: #888;
|
||||
box-shadow: 0 8px 32px 0 rgba(102, 102, 102, 0.18);
|
||||
transform: translateY(-2px) scale(1.06);
|
||||
}
|
||||
&:active {
|
||||
background: #555;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.vab-spotlight-card {
|
||||
width: 98vw;
|
||||
min-width: 0;
|
||||
padding: 0 0 8px 0;
|
||||
}
|
||||
.vab-spotlight-input-wrap {
|
||||
padding: 18px 10px 10px 10px;
|
||||
}
|
||||
.vab-spotlight-fab {
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
.vab-search-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 22px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: color 0.18s;
|
||||
&:hover {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
.vab-spotlight-history-wrap {
|
||||
padding: 0 28px 0 28px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.vab-spotlight-history-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
}
|
||||
.vab-spotlight-history-clear {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
.vab-spotlight-history-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.vab-spotlight-history-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 12px 2px 10px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
background: #f5f7fa;
|
||||
border-radius: 16px;
|
||||
transition:
|
||||
background 0.18s,
|
||||
color 0.18s;
|
||||
&:hover {
|
||||
color: #666;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
.vab-spotlight-history-del {
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
color: #bbb;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
214
library/components/VabSideBar/index.vue
Normal file
214
library/components/VabSideBar/index.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<script lang="ts" setup>
|
||||
import variables from '@vab/styles/variables/variables.module.scss'
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import { defaultOpeneds, uniqueOpened } from '@/config'
|
||||
|
||||
const props = defineProps({
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'vertical',
|
||||
},
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { collapse } = storeToRefs(settingsStore)
|
||||
const routesStore = useRoutesStore()
|
||||
const {
|
||||
getRoutes: routes,
|
||||
getActiveMenu: activeMenu,
|
||||
getPartialRoutes: partialRoutes,
|
||||
} = storeToRefs(routesStore)
|
||||
|
||||
const handleRoutes = computed(() => {
|
||||
return props.layout === 'comprehensive'
|
||||
? partialRoutes.value
|
||||
: routes.value.flatMap((route: any) =>
|
||||
route.meta.levelHidden && route.children
|
||||
? [...route.children]
|
||||
: route
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-scrollbar
|
||||
class="vab-side-bar"
|
||||
:class="{
|
||||
'is-collapse': collapse,
|
||||
'side-bar-common': layout === 'common',
|
||||
}"
|
||||
>
|
||||
<vab-logo
|
||||
v-if="
|
||||
layout === 'vertical' ||
|
||||
layout === 'comprehensive' ||
|
||||
layout === 'float'
|
||||
"
|
||||
/>
|
||||
<el-menu
|
||||
:active-text-color="variables['menu-color-active']"
|
||||
:background-color="variables['menu-background']"
|
||||
:collapse="collapse"
|
||||
:collapse-transition="false"
|
||||
:default-active="activeMenu.data"
|
||||
:default-openeds="defaultOpeneds"
|
||||
menu-trigger="click"
|
||||
mode="vertical"
|
||||
:text-color="variables['menu-color']"
|
||||
:unique-opened="uniqueOpened"
|
||||
>
|
||||
<template
|
||||
v-for="(item, index) in handleRoutes"
|
||||
:key="index + item.name"
|
||||
>
|
||||
<vab-menu v-if="!item.meta.hidden" :item="item" />
|
||||
</template>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin active {
|
||||
&:hover {
|
||||
color: var(--el-color-white);
|
||||
background-color: $base-menu-active;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-white);
|
||||
background-color: $base-menu-active;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-side-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: $base-z-index + 1;
|
||||
width: var(--el-left-menu-width);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: $base-menu-background;
|
||||
box-shadow: $base-box-shadow;
|
||||
transition: $base-transition;
|
||||
|
||||
&.side-bar-common {
|
||||
top: $base-header-height;
|
||||
height: calc(100vh - #{$base-header-height});
|
||||
}
|
||||
|
||||
&.is-collapse {
|
||||
width: $base-left-menu-width-min;
|
||||
border-right: 0;
|
||||
|
||||
:deep() {
|
||||
.el-menu--collapse.el-menu {
|
||||
> .el-menu-item,
|
||||
> .el-sub-menu {
|
||||
text-align: center;
|
||||
|
||||
.el-tag {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-menu--collapse {
|
||||
border-right: 0;
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
right: 10px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep() {
|
||||
.el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: $base-menu-item-height;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: $base-menu-item-height;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
|
||||
i {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--由于element-plus
|
||||
bug使用teleported=false会导致多级路由无法显示,故所有菜单必须生成至body下,样式必须放到body下-->
|
||||
<style lang="scss">
|
||||
@mixin menuActiveHover {
|
||||
&:hover,
|
||||
&.is-active {
|
||||
i {
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
|
||||
color: var(--el-color-white);
|
||||
background: var(--el-color-primary);
|
||||
|
||||
.el-sub-menu__title {
|
||||
i {
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
|
||||
color: var(--el-color-white);
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.el-popper {
|
||||
.el-menu--vertical {
|
||||
.el-menu-item,
|
||||
.el-sub-menu {
|
||||
height: $base-menu-item-height;
|
||||
text-overflow: ellipsis;
|
||||
line-height: $base-menu-item-height;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
@include menuActiveHover;
|
||||
|
||||
i {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.el-sub-menu__title {
|
||||
height: $base-menu-item-height;
|
||||
text-overflow: ellipsis;
|
||||
line-height: $base-menu-item-height;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
@include menuActiveHover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
614
library/components/VabTabs/index.vue
Normal file
614
library/components/VabTabs/index.vue
Normal file
@@ -0,0 +1,614 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTabsStore } from '@/store/modules/tabs'
|
||||
import { useRoutesStore } from '@/store/modules/routes'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import { handleActivePath, handleTabs } from '@/utils/routes'
|
||||
import { translate } from '@/i18n'
|
||||
import { VabRoute, VabRouteRecord } from '/#/router'
|
||||
|
||||
defineProps({
|
||||
layout: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const $pub: any = inject('$pub')
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
const routesStore = useRoutesStore()
|
||||
const { getRoutes: routes } = storeToRefs(routesStore)
|
||||
const tabsStore = useTabsStore()
|
||||
const { getVisitedRoutes: visitedRoutes } = storeToRefs(tabsStore)
|
||||
const {
|
||||
addVisitedRoute,
|
||||
delVisitedRoute,
|
||||
delOthersVisitedRoutes,
|
||||
delLeftVisitedRoutes,
|
||||
delRightVisitedRoutes,
|
||||
delAllVisitedRoutes,
|
||||
} = tabsStore
|
||||
|
||||
const tabActive = ref('')
|
||||
const active = ref(false)
|
||||
|
||||
const hoverRoute = ref()
|
||||
const visible = ref(false)
|
||||
const top = ref(0)
|
||||
const left = ref(0)
|
||||
|
||||
const isActive = (path: string) => path === handleActivePath(route, true)
|
||||
const isNoCLosable = (tag: any) => tag.meta.noClosable
|
||||
const handleTabClick = (tab: any) => {
|
||||
if (!isActive(tab.name)) router.push(visitedRoutes.value[tab.index])
|
||||
}
|
||||
const handleVisibleChange = (val: boolean) => {
|
||||
active.value = val
|
||||
}
|
||||
const initNoCLosableTabs = (routes: VabRouteRecord[]) => {
|
||||
routes.forEach((_route) => {
|
||||
if (_route.meta.noClosable) addTabs(_route)
|
||||
if (_route.children) initNoCLosableTabs(_route.children)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 添加标签页
|
||||
* @param tag route
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const addTabs = async (tag: VabRoute | VabRouteRecord) => {
|
||||
const tab = handleTabs(tag)
|
||||
if (tab) {
|
||||
await addVisitedRoute(tab)
|
||||
tabActive.value = tab.path
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 根据原生路径删除标签中的标签
|
||||
* @param rawPath 原生路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const handleTabRemove: any = async (rawPath: string) => {
|
||||
if (isActive(rawPath)) await toLastTab()
|
||||
await delVisitedRoute(rawPath)
|
||||
}
|
||||
const handleCommand = (command: string) => {
|
||||
switch (command) {
|
||||
case 'refreshThisTab':
|
||||
refreshThisTab()
|
||||
break
|
||||
case 'closeOthersTabs':
|
||||
closeOthersTabs()
|
||||
break
|
||||
case 'closeLeftTabs':
|
||||
closeLeftTabs()
|
||||
break
|
||||
case 'closeRightTabs':
|
||||
closeRightTabs()
|
||||
break
|
||||
case 'closeAllTabs':
|
||||
closeAllTabs()
|
||||
break
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 刷新当前标签页
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const refreshThisTab = () => {
|
||||
$pub('reload-router-view')
|
||||
}
|
||||
/**
|
||||
* 删除其他标签页
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const closeOthersTabs = async () => {
|
||||
if (hoverRoute.value) {
|
||||
await router.push(hoverRoute.value)
|
||||
await delOthersVisitedRoutes(hoverRoute.value.path)
|
||||
} else await delOthersVisitedRoutes(handleActivePath(route, true))
|
||||
await closeMenu()
|
||||
}
|
||||
/**
|
||||
* 删除左侧标签页
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const closeLeftTabs = async () => {
|
||||
if (hoverRoute.value) {
|
||||
await router.push(hoverRoute.value)
|
||||
await delLeftVisitedRoutes(hoverRoute.value.path)
|
||||
} else await delLeftVisitedRoutes(handleActivePath(route, true))
|
||||
await closeMenu()
|
||||
}
|
||||
/**
|
||||
* 删除右侧标签页
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const closeRightTabs = async () => {
|
||||
if (hoverRoute.value) {
|
||||
await router.push(hoverRoute.value)
|
||||
await delRightVisitedRoutes(hoverRoute.value.path)
|
||||
} else await delRightVisitedRoutes(handleActivePath(route, true))
|
||||
await closeMenu()
|
||||
}
|
||||
/**
|
||||
* 删除所有标签页
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const closeAllTabs = async () => {
|
||||
await delAllVisitedRoutes()
|
||||
await toLastTab()
|
||||
await closeMenu()
|
||||
}
|
||||
/**
|
||||
* 跳转最后一个标签页
|
||||
*/
|
||||
const toLastTab = async () => {
|
||||
const latestView = visitedRoutes.value
|
||||
.filter((_: any) => _.path !== handleActivePath(route, true))
|
||||
.slice(-1)[0]
|
||||
if (latestView) await router.push(latestView)
|
||||
else await router.push('/')
|
||||
}
|
||||
|
||||
const { x, y } = useMouse()
|
||||
const openMenu = () => {
|
||||
left.value = x.value
|
||||
top.value = y.value
|
||||
visible.value = true
|
||||
}
|
||||
const closeMenu = () => {
|
||||
visible.value = false
|
||||
hoverRoute.value = null
|
||||
}
|
||||
|
||||
initNoCLosableTabs(routes.value)
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
addTabs(route)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
watchEffect(() => {
|
||||
if (visible.value) document.body.addEventListener('click', closeMenu)
|
||||
else document.body.removeEventListener('click', closeMenu)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vab-tabs">
|
||||
<vab-fold v-if="layout === 'common'" />
|
||||
<el-tabs
|
||||
v-model="tabActive"
|
||||
class="vab-tabs-content"
|
||||
:class="{
|
||||
['vab-tabs-content-' + theme.tabsBarStyle]: true,
|
||||
}"
|
||||
type="card"
|
||||
@tab-click="handleTabClick"
|
||||
@tab-remove="handleTabRemove"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="item in visitedRoutes"
|
||||
:key="item.path"
|
||||
:closable="!isNoCLosable(item)"
|
||||
:name="item.path"
|
||||
>
|
||||
<template #label>
|
||||
<span
|
||||
style="display: inline-block"
|
||||
@contextmenu.prevent="openMenu"
|
||||
>
|
||||
<template v-if="theme.showTabsIcon">
|
||||
<vab-icon
|
||||
v-if="item.meta && item.meta.icon"
|
||||
:icon="item.meta.icon"
|
||||
:is-custom-svg="item.meta.isCustomSvg"
|
||||
/>
|
||||
<!-- 如果没有图标那么取第二级的图标 -->
|
||||
<vab-icon v-else :icon="item.parentIcon" />
|
||||
</template>
|
||||
<span v-if="item.meta && item.meta.title">
|
||||
{{ translate(item.meta.title) }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-dropdown
|
||||
placement="bottom-end"
|
||||
popper-class="vab-tabs-more-dropdown"
|
||||
@command="handleCommand"
|
||||
@visible-change="handleVisibleChange"
|
||||
>
|
||||
<span
|
||||
class="vab-tabs-more"
|
||||
:class="{ 'vab-tabs-more-active': active }"
|
||||
>
|
||||
<span class="vab-tabs-more-icon">
|
||||
<i class="box box-t"></i>
|
||||
<i class="box box-b"></i>
|
||||
</span>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="tabs-more">
|
||||
<el-dropdown-item command="refreshThisTab">
|
||||
<vab-icon icon="refresh-line" />
|
||||
<span>
|
||||
{{ translate('刷新') }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="closeOthersTabs">
|
||||
<vab-icon icon="close-line" />
|
||||
<span>
|
||||
{{ translate('关闭其他') }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="closeLeftTabs">
|
||||
<vab-icon icon="arrow-left-line" />
|
||||
<span>
|
||||
{{ translate('关闭左侧') }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="closeRightTabs">
|
||||
<vab-icon icon="arrow-right-line" />
|
||||
<span>
|
||||
{{ translate('关闭右侧') }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="closeAllTabs">
|
||||
<vab-icon icon="close-line" />
|
||||
<span>
|
||||
{{ translate('关闭全部') }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<ul
|
||||
v-if="visible"
|
||||
class="contextmenu el-dropdown-menu"
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
>
|
||||
<li class="el-dropdown-menu__item" @click="refreshThisTab">
|
||||
<vab-icon icon="refresh-line" />
|
||||
<span>{{ translate('刷新') }}</span>
|
||||
</li>
|
||||
<li
|
||||
class="el-dropdown-menu__item"
|
||||
:class="{ 'is-disabled': visitedRoutes.length === 1 }"
|
||||
@click="closeOthersTabs"
|
||||
>
|
||||
<vab-icon icon="close-line" />
|
||||
<span>{{ translate('关闭其他') }}</span>
|
||||
</li>
|
||||
<li
|
||||
class="el-dropdown-menu__item"
|
||||
:class="{ 'is-disabled': !visitedRoutes.indexOf(hoverRoute) }"
|
||||
@click="closeLeftTabs"
|
||||
>
|
||||
<vab-icon icon="arrow-left-line" />
|
||||
<span>{{ translate('关闭左侧') }}</span>
|
||||
</li>
|
||||
<li
|
||||
class="el-dropdown-menu__item"
|
||||
:class="{
|
||||
'is-disabled':
|
||||
visitedRoutes.indexOf(hoverRoute) ===
|
||||
visitedRoutes.length - 1,
|
||||
}"
|
||||
@click="closeRightTabs"
|
||||
>
|
||||
<vab-icon icon="arrow-right-line" />
|
||||
<span>{{ translate('关闭右侧') }}</span>
|
||||
</li>
|
||||
<li class="el-dropdown-menu__item" @click="closeAllTabs">
|
||||
<vab-icon icon="close-line" />
|
||||
<span>{{ translate('关闭全部') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'sass:math';
|
||||
.vab-tabs {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: $base-tabs-height;
|
||||
padding-right: $base-padding;
|
||||
padding-left: $base-padding;
|
||||
user-select: none;
|
||||
background: var(--el-color-white);
|
||||
border-top: 1px solid #f6f6f6;
|
||||
|
||||
:deep() {
|
||||
.fold-unfold {
|
||||
margin-right: $base-margin;
|
||||
}
|
||||
|
||||
[class*='ri'] {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.vab-icon {
|
||||
vertical-align: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
width: calc(100% - 40px);
|
||||
|
||||
&-card {
|
||||
height: #{$base-tag-item-height};
|
||||
|
||||
:deep() {
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
height: #{$base-tag-item-height};
|
||||
line-height: #{$base-tag-item-height};
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
margin: 0 0 1px 0;
|
||||
border-bottom: 0;
|
||||
|
||||
.el-tabs__nav {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
box-sizing: border-box;
|
||||
height: #{$base-tag-item-height};
|
||||
margin-top: 1px;
|
||||
margin-right: 5px;
|
||||
line-height: #{$base-tag-item-height};
|
||||
border: 1px solid #{$base-border-color} !important;
|
||||
border-radius: #{$base-border-radius};
|
||||
transition: padding 0.3s
|
||||
cubic-bezier(0.645, 0.045, 0.355, 1) !important;
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary);
|
||||
outline: none;
|
||||
background: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-smart {
|
||||
height: #{$base-tag-item-height};
|
||||
|
||||
:deep() {
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
height: #{$base-tag-item-height};
|
||||
line-height: #{$base-tag-item-height};
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
margin: 0 0 1px 0;
|
||||
border-bottom: 0;
|
||||
|
||||
.el-tabs__nav {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
height: #{$base-tag-item-height};
|
||||
margin-top: 1px;
|
||||
margin-right: 5px;
|
||||
line-height: #{$base-tag-item-height};
|
||||
outline: none;
|
||||
border: 0;
|
||||
transition: padding 0.3s
|
||||
cubic-bezier(0.645, 0.045, 0.355, 1) !important;
|
||||
|
||||
&.is-active {
|
||||
outline: none;
|
||||
background: var(--el-color-primary-light-9);
|
||||
|
||||
&:after {
|
||||
width: 100%;
|
||||
transition: #{$base-transition};
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
content: '';
|
||||
background-color: var(--el-color-primary);
|
||||
transition: #{$base-transition};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-primary-light-9);
|
||||
|
||||
&:after {
|
||||
width: 100%;
|
||||
transition: #{$base-transition};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-smooth {
|
||||
height: #{$base-tag-item-height} + 4;
|
||||
|
||||
:deep() {
|
||||
.el-tabs__nav-next,
|
||||
.el-tabs__nav-prev {
|
||||
height: #{$base-tag-item-height} + 14;
|
||||
line-height: #{$base-tag-item-height} + 14;
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
margin: 0 0 -1px 0;
|
||||
border-bottom: 0;
|
||||
|
||||
.el-tabs__nav {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
height: #{$base-tag-item-height} + 4;
|
||||
padding: 0 30px 0 30px;
|
||||
margin-top: math.div(
|
||||
$base-tabs-height - $base-tag-item-height - 3.1,
|
||||
2
|
||||
);
|
||||
margin-right: -18px;
|
||||
line-height: #{$base-tag-item-height} + 4;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
border: 0;
|
||||
transition: padding 0.3s
|
||||
cubic-bezier(0.645, 0.045, 0.355, 1) !important;
|
||||
&.is-closable:hover {
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
padding: 0 30px 0 30px;
|
||||
color: var(--el-color-primary);
|
||||
outline: none;
|
||||
background: var(--el-color-primary-light-9);
|
||||
mask: url('~@/assets/tabs_images/vab-tab.png');
|
||||
mask-size: 100% 100%;
|
||||
|
||||
&:hover {
|
||||
padding: 0 30px 0 30px;
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
mask: url('~@/assets/tabs_images/vab-tab.png');
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
&.is-closable {
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
padding: 0 30px 0 30px;
|
||||
color: var(--el-color-black);
|
||||
background: #dee1e6;
|
||||
mask: url('~@/assets/tabs_images/vab-tab.png');
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contextmenu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
.el-dropdown-menu__item:hover {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
|
||||
&-more {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
text-align: left;
|
||||
|
||||
&-active,
|
||||
&:hover {
|
||||
&:after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 0;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.vab-tabs-more-icon {
|
||||
transform: rotate(90deg);
|
||||
|
||||
.box-t {
|
||||
&:before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.box:before,
|
||||
.box:after {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
display: inline-block;
|
||||
color: #9a9a9a;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease-out;
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 14px;
|
||||
height: 8px;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
content: '';
|
||||
background: #9a9a9a;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 8px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
content: '';
|
||||
background: #9a9a9a;
|
||||
}
|
||||
}
|
||||
|
||||
.box-t {
|
||||
&:before {
|
||||
transition: transform 0.3s ease-out 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
515
library/components/VabTheme/components/VabThemeDrawer.vue
Normal file
515
library/components/VabTheme/components/VabThemeDrawer.vue
Normal file
@@ -0,0 +1,515 @@
|
||||
<script lang="ts" setup>
|
||||
import { translate } from '@/i18n'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const $sub: any = inject('$sub')
|
||||
const $unsub: any = inject('$unsub')
|
||||
const $baseLoading: any = inject('$baseLoading')
|
||||
|
||||
const settingsStore: any = useSettingsStore()
|
||||
const { theme, device }: any = storeToRefs(settingsStore)
|
||||
const { saveTheme, resetTheme, updateTheme }: any = settingsStore
|
||||
|
||||
const state = reactive({
|
||||
drawerVisible: false,
|
||||
})
|
||||
|
||||
const handleOpenTheme = () => {
|
||||
state.drawerVisible = true
|
||||
}
|
||||
|
||||
const updateMenuWidth = () => {
|
||||
useCssVar('--el-left-menu-width', ref(null)).value =
|
||||
theme.value.menuWidth
|
||||
}
|
||||
|
||||
const setDefaultTheme = async () => {
|
||||
await resetTheme()
|
||||
await updateTheme()
|
||||
state.drawerVisible = false
|
||||
if (document.body.getBoundingClientRect().width - 1 < 992)
|
||||
location.reload()
|
||||
}
|
||||
|
||||
const handleSaveTheme = async () => {
|
||||
await saveTheme()
|
||||
state.drawerVisible = false
|
||||
if (document.body.getBoundingClientRect().width - 1 < 992)
|
||||
location.reload()
|
||||
}
|
||||
|
||||
const shuffle = (val: string | boolean, list: (string | boolean)[]) =>
|
||||
list.filter((_) => _ !== val)[(Math.random() * (list.length - 1)) | 0]
|
||||
|
||||
const randomTheme = async () => {
|
||||
const loading = $baseLoading(0)
|
||||
// 随机换肤重置移除主题,防止代码更新影响样式
|
||||
await resetTheme()
|
||||
|
||||
theme.value.themeName = shuffle(theme.value.themeName, [
|
||||
'blue-black',
|
||||
'blue-white',
|
||||
'ocean',
|
||||
'green-black',
|
||||
'green-white',
|
||||
'purple-black',
|
||||
'purple-white',
|
||||
'red-black',
|
||||
'red-white',
|
||||
])
|
||||
theme.value.columnStyle = shuffle(theme.value.columnStyle, [
|
||||
'vertical',
|
||||
'horizontal',
|
||||
'card',
|
||||
'arrow',
|
||||
])
|
||||
// theme.value.background = shuffle(theme.value.background, [
|
||||
// 'none',
|
||||
// 'vab-background',
|
||||
// ])
|
||||
theme.value.tabsBarStyle = shuffle(theme.value.tabsBarStyle, [
|
||||
'card',
|
||||
'smart',
|
||||
'smooth',
|
||||
])
|
||||
theme.value.showTabsIcon = shuffle(theme.value.showTabsIcon, [
|
||||
true,
|
||||
false,
|
||||
])
|
||||
theme.value.layout =
|
||||
device.value === 'desktop'
|
||||
? shuffle(theme.value.layout, [
|
||||
'horizontal',
|
||||
'vertical',
|
||||
'column',
|
||||
'comprehensive',
|
||||
'common',
|
||||
'float',
|
||||
])
|
||||
: 'vertical'
|
||||
|
||||
try {
|
||||
await updateTheme()
|
||||
await saveTheme()
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
loading.close()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
$sub('theme', () => {
|
||||
handleOpenTheme()
|
||||
})
|
||||
$sub('random-theme', () => {
|
||||
randomTheme()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
$unsub('theme')
|
||||
$unsub('random-theme')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="state.drawerVisible"
|
||||
append-to-body
|
||||
class="vab-drawer"
|
||||
direction="rtl"
|
||||
size="285px"
|
||||
:title="translate('主题配置')"
|
||||
>
|
||||
<el-scrollbar height="88vh">
|
||||
<el-form ref="form" label-position="left" :model="theme">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
{{ translate('布局') }}
|
||||
<el-tooltip
|
||||
:content="
|
||||
translate(
|
||||
'布局配置仅' +
|
||||
'在电脑视窗下生效,手机视窗时将' +
|
||||
'默认锁定为纵向布局'
|
||||
)
|
||||
"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
>
|
||||
<vab-icon icon="question-line" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-select
|
||||
v-model="theme.layout"
|
||||
:disabled="device === 'mobile'"
|
||||
placeholder="请选择"
|
||||
>
|
||||
<el-option
|
||||
key="column"
|
||||
:label="translate('分栏')"
|
||||
value="column"
|
||||
/>
|
||||
<el-option
|
||||
key="comprehensive"
|
||||
:label="translate('综合')"
|
||||
value="comprehensive"
|
||||
/>
|
||||
<el-option
|
||||
key="vertical"
|
||||
:label="translate('纵向')"
|
||||
value="vertical"
|
||||
/>
|
||||
<el-option
|
||||
key="horizontal"
|
||||
:label="translate('横向')"
|
||||
value="horizontal"
|
||||
/>
|
||||
<el-option
|
||||
key="common"
|
||||
:label="translate('常规')"
|
||||
value="common"
|
||||
/>
|
||||
<el-option
|
||||
key="float"
|
||||
:label="translate('浮动')"
|
||||
value="float"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('主题')">
|
||||
<el-select v-model="theme.themeName" @change="updateTheme">
|
||||
<el-option
|
||||
key="blue-black"
|
||||
:label="translate('蓝黑')"
|
||||
value="blue-black"
|
||||
/>
|
||||
<el-option
|
||||
key="blue-white"
|
||||
:label="translate('蓝白')"
|
||||
value="blue-white"
|
||||
/>
|
||||
<el-option
|
||||
key="green-black"
|
||||
:label="translate('绿黑')"
|
||||
value="green-black"
|
||||
/>
|
||||
<el-option
|
||||
key="green-white"
|
||||
:label="translate('绿白')"
|
||||
value="green-white"
|
||||
/>
|
||||
<el-option
|
||||
key="purple-black"
|
||||
:label="translate('紫黑')"
|
||||
value="purple-black"
|
||||
/>
|
||||
<el-option
|
||||
key="purple-white"
|
||||
:label="translate('紫白')"
|
||||
value="purple-white"
|
||||
/>
|
||||
<!-- 红黑、红白主题完成群文档任务免费获取 -->
|
||||
<el-option
|
||||
key="red-black"
|
||||
:label="translate('红黑')"
|
||||
value="red-black"
|
||||
/>
|
||||
<el-option
|
||||
key="red-white"
|
||||
:label="translate('红白')"
|
||||
value="red-white"
|
||||
/>
|
||||
<el-option
|
||||
key="ocean"
|
||||
:label="translate('渐变')"
|
||||
value="ocean"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="vab-item-custom">
|
||||
<template #label>
|
||||
{{ translate('菜单背景') }}
|
||||
<el-tooltip
|
||||
:content="
|
||||
translate(
|
||||
'支持纵向布局' +
|
||||
'、分栏布局、综合' +
|
||||
'布局、常规布局,不支持横' +
|
||||
'向布局、浮动' +
|
||||
'布局'
|
||||
)
|
||||
"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
>
|
||||
<vab-icon icon="question-line" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-radio-group
|
||||
v-model="theme.background"
|
||||
:disabled="
|
||||
theme.layout === 'float' ||
|
||||
theme.layout === 'horizontal'
|
||||
"
|
||||
@change="updateTheme"
|
||||
>
|
||||
<el-radio-button
|
||||
class="none"
|
||||
label="none"
|
||||
value="none"
|
||||
/>
|
||||
<el-radio-button
|
||||
class="vab-background"
|
||||
label="vab-background"
|
||||
value="vab-background"
|
||||
/>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('菜单宽度')">
|
||||
<el-select
|
||||
v-model="theme.menuWidth"
|
||||
:disabled="
|
||||
theme.layout === 'float' ||
|
||||
theme.layout === 'horizontal'
|
||||
"
|
||||
@change="updateMenuWidth"
|
||||
>
|
||||
<el-option key="266px" label="266px" value="266px" />
|
||||
<el-option key="277px" label="277px" value="277px" />
|
||||
<el-option key="288px" label="288px" value="288px" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('标签')">
|
||||
<el-switch v-model="theme.showTabs" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
{{ translate('标签图标') }}
|
||||
<el-tooltip
|
||||
:content="translate('标签开启时生效')"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
>
|
||||
<vab-icon icon="question-line" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-switch
|
||||
v-model="theme.showTabsIcon"
|
||||
:disabled="!theme.showTabs"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
{{ translate('标签风格') }}
|
||||
<el-tooltip
|
||||
:content="translate('标签开启时生效')"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
>
|
||||
<vab-icon icon="question-line" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-select
|
||||
v-model="theme.tabsBarStyle"
|
||||
:disabled="!theme.showTabs"
|
||||
>
|
||||
<el-option
|
||||
key="card"
|
||||
:label="translate('卡片')"
|
||||
value="card"
|
||||
/>
|
||||
<el-option
|
||||
key="smart"
|
||||
:label="translate('灵动')"
|
||||
value="smart"
|
||||
/>
|
||||
<el-option
|
||||
key="smooth"
|
||||
:label="translate('圆滑')"
|
||||
value="smooth"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
{{ translate('分栏风格') }}
|
||||
<el-tooltip
|
||||
:content="translate('分栏布局时生效')"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
>
|
||||
<vab-icon icon="question-line" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-select
|
||||
v-model="theme.columnStyle"
|
||||
:disabled="theme.layout !== 'column'"
|
||||
>
|
||||
<el-option
|
||||
key="vertical"
|
||||
:label="translate('纵向')"
|
||||
value="vertical"
|
||||
/>
|
||||
<el-option
|
||||
key="horizontal"
|
||||
:label="translate('横向')"
|
||||
value="horizontal"
|
||||
/>
|
||||
<el-option
|
||||
key="card"
|
||||
:label="translate('卡片')"
|
||||
value="card"
|
||||
/>
|
||||
<el-option
|
||||
key="arrow"
|
||||
:label="translate('箭头')"
|
||||
value="arrow"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('头部固定')">
|
||||
<el-switch
|
||||
v-model="theme.fixedHeader"
|
||||
:disabled="theme.layout === 'common'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('国际化')">
|
||||
<el-switch v-model="theme.showLanguage" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('进度条')">
|
||||
<el-switch v-model="theme.showProgressBar" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('刷新')">
|
||||
<el-switch v-model="theme.showRefresh" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('搜索')">
|
||||
<el-switch v-model="theme.showSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('通知')">
|
||||
<el-switch v-model="theme.showNotice" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('全屏')">
|
||||
<el-switch v-model="theme.showFullScreen" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('锁屏')">
|
||||
<el-switch v-model="theme.showLock" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="translate('页面动画')">
|
||||
<el-switch v-model="theme.showPageTransition" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleSaveTheme">
|
||||
{{ translate('保存') }}
|
||||
</el-button>
|
||||
<el-button @click="setDefaultTheme">
|
||||
{{ translate('恢复默认') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use 'sass:math';
|
||||
.vab-drawer {
|
||||
// position: relative;
|
||||
.el-drawer__header {
|
||||
padding: $base-padding $base-padding 0 $base-padding;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
padding-right: 0;
|
||||
|
||||
.el-scrollbar__wrap {
|
||||
height: calc(100vh - 80px);
|
||||
padding-right: $base-padding;
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: $base-margin * 2 0 $base-margin * 2 0;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__label {
|
||||
flex: 1 1;
|
||||
i {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&.vab-item-custom {
|
||||
display: block !important;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.el-radio-button {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
|
||||
&.is-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
box-shadow: 0 0 2px 2px #1890ff;
|
||||
}
|
||||
|
||||
.el-radio-button__orig-radio,
|
||||
.el-radio-button__inner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.none {
|
||||
font-family: 'remixicon', sans-serif !important;
|
||||
font-size: 16px;
|
||||
font-weight: 580;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
background: #f7f7f7 none;
|
||||
background-size: cover;
|
||||
|
||||
&:before {
|
||||
content: '\eace';
|
||||
}
|
||||
}
|
||||
|
||||
&.vab-background {
|
||||
background: url(~@/assets/theme_images/background-1.png);
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
width: 115px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-drawer__footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: $base-z-index + 1;
|
||||
display: flex;
|
||||
padding: math.div($base-padding, 2);
|
||||
background: var(--el-color-white);
|
||||
border-top: 1px solid #{$base-border-color};
|
||||
}
|
||||
}
|
||||
</style>
|
||||
168
library/components/VabTheme/components/VabThemeSetting.vue
Normal file
168
library/components/VabTheme/components/VabThemeSetting.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
import { translate } from '@/i18n'
|
||||
|
||||
const $pub: any = inject('$pub')
|
||||
//const $baseMessage: any = inject('$baseMessage')
|
||||
|
||||
const settingsStore: any = useSettingsStore()
|
||||
const { theme }: any = storeToRefs(settingsStore)
|
||||
|
||||
const handleOpenTheme = () => {
|
||||
$pub('theme')
|
||||
}
|
||||
const randomTheme = () => {
|
||||
$pub('random-theme')
|
||||
}
|
||||
const buy = () => {
|
||||
window.open('https://vuejs-core.cn/authorization')
|
||||
}
|
||||
// const getCode = () => {
|
||||
// if (process.env.NODE_ENV === 'development') $pub('get-code')
|
||||
// else
|
||||
// ElMessageBox.prompt(
|
||||
// '请输入密码(密码在购买时获得,跳转后需登录购买时绑定的github账号)',
|
||||
// '温馨提示',
|
||||
// {
|
||||
// confirmButtonText: '确定',
|
||||
// cancelButtonText: '取消',
|
||||
// }
|
||||
// )
|
||||
// .then(({ value }) => {
|
||||
// if (value !== 'vabp') {
|
||||
// $baseMessage(
|
||||
// '密码不正确!',
|
||||
// 'error',
|
||||
// 'vab-hey-message-error'
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
// $pub('get-code')
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// }
|
||||
|
||||
const removeLocalStorage = () => {
|
||||
localStorage.clear()
|
||||
location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul v-if="theme.showThemeSetting" class="vab-theme-setting">
|
||||
<li @click="handleOpenTheme">
|
||||
<a>
|
||||
<vab-icon icon="brush-2-line" />
|
||||
<p>{{ translate('主题配置') }}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li @click="randomTheme">
|
||||
<a>
|
||||
<vab-icon icon="apps-line" />
|
||||
<p>{{ translate('随机换肤') }}</p>
|
||||
</a>
|
||||
</li>
|
||||
<li @click="buy">
|
||||
<a>
|
||||
<vab-icon icon="shopping-cart-2-line" />
|
||||
<p>{{ translate('购买源码') }}</p>
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li @click="getCode">
|
||||
<a>
|
||||
<vab-icon icon="file-copy-line" />
|
||||
<p>{{ translate('拷贝源码') }}</p>
|
||||
</a>
|
||||
</li> -->
|
||||
<li @click="removeLocalStorage">
|
||||
<a>
|
||||
<vab-icon icon="delete-bin-4-line" />
|
||||
<p>
|
||||
{{ translate('清理缓存') }}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-theme-setting {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
z-index: $base-z-index - 2;
|
||||
padding: 10px 0 0 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: var(--el-color-white);
|
||||
border: 1px solid #{$base-border-color};
|
||||
border-top-left-radius: $base-border-radius + 3;
|
||||
border-bottom-left-radius: $base-border-radius + 3;
|
||||
box-shadow: 0 0 50px 0 rgb(82 63 105 / 15%);
|
||||
transform: translateY(-50%);
|
||||
|
||||
> li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px 10px 10px;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
|
||||
&:nth-child(2) {
|
||||
[class*='ri-'] {
|
||||
animation: rotate 6s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
$colors: (
|
||||
1: #3698fd,
|
||||
2: #1bc3bb,
|
||||
3: #faa500,
|
||||
4: #b37feb,
|
||||
5: #ef4c5d,
|
||||
);
|
||||
|
||||
@each $key, $color in $colors {
|
||||
&:nth-child(#{$key}) {
|
||||
a {
|
||||
color: $color;
|
||||
background: mix($base-color-white, $color, 90%);
|
||||
transition:
|
||||
color 0.15s ease,
|
||||
background-color 0.15s ease,
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease,
|
||||
-webkit-box-shadow 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-white);
|
||||
background: $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
background: #f6f8f9;
|
||||
border-radius: $base-border-radius + 3;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: $base-font-size-small;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
18
library/components/VabTheme/index.vue
Normal file
18
library/components/VabTheme/index.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const $pub: any = inject('$pub')
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
|
||||
const handleOpenTheme = () => {
|
||||
$pub('theme')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="theme.showTheme">
|
||||
<vab-icon icon="brush-2-line" @click="handleOpenTheme" />
|
||||
</span>
|
||||
</template>
|
||||
39
library/index.ts
Normal file
39
library/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { App } from 'vue'
|
||||
|
||||
// 加载雪碧图
|
||||
import '@/icon'
|
||||
// 加载全局样式样式
|
||||
import './styles/vab.scss'
|
||||
|
||||
import { createHead } from '@vueuse/head'
|
||||
|
||||
// 加载Icon
|
||||
import VabIcon from 'vab-icons'
|
||||
import 'vab-icons/lib/vab-icons.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
const name = process['env']['VUE_' + 'APP_' + 'GITHUB_' + 'USER_' + 'NAME']
|
||||
const noTest = name !== 'test'
|
||||
const noEmpty = name !== 'undefined'
|
||||
const dev = process['env']['NODE_' + 'ENV'] === 'dev' + 'elop' + 'ment'
|
||||
|
||||
export function setupVab(app: App<Element>) {
|
||||
if ((noTest && noEmpty && !dev && VabIcon) || (dev && VabIcon)) {
|
||||
app.use(createHead())
|
||||
|
||||
app.component('VabIcon', VabIcon)
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
// 加载背景
|
||||
const Themes = require.context('./styles/background', false, /\.scss$/)
|
||||
Themes.keys().map(Themes)
|
||||
|
||||
// 加载插件
|
||||
const Plugins = require.context('./plugins', true, /\.ts$/)
|
||||
Plugins.keys().forEach((key) => {
|
||||
app.use(Plugins(key).default)
|
||||
})
|
||||
}
|
||||
}
|
||||
78
library/layouts/VabLayoutColumn/index.vue
Normal file
78
library/layouts/VabLayoutColumn/index.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<!--分栏布局 -->
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
fixedHeader: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
showTabs: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { theme } = storeToRefs(settingsStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vab-layout-column"
|
||||
:class="{
|
||||
fixed: fixedHeader,
|
||||
'no-tabs-bar': !showTabs,
|
||||
}"
|
||||
>
|
||||
<vab-column-bar />
|
||||
<div
|
||||
class="vab-main"
|
||||
:class="{
|
||||
['vab-main-' + theme.columnStyle]: true,
|
||||
'is-collapse-main': collapse,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="vab-layout-header"
|
||||
:class="{
|
||||
'fixed-header': fixedHeader,
|
||||
}"
|
||||
>
|
||||
<vab-nav />
|
||||
<vab-tabs v-show="showTabs" />
|
||||
</div>
|
||||
<vab-app-main />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-layout-column {
|
||||
.vab-main {
|
||||
&.is-collapse-main {
|
||||
&.vab-main-horizontal {
|
||||
margin-left: $base-left-menu-width-min * 1.3;
|
||||
|
||||
:deep() {
|
||||
.fixed-header {
|
||||
width: calc(
|
||||
100% - #{$base-left-menu-width-min} * 1.3
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
88
library/layouts/VabLayoutCommon/index.vue
Normal file
88
library/layouts/VabLayoutCommon/index.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<!--常规布局 -->
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
fixedHeader: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
showTabs: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
device: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'desktop'
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vab-layout-common"
|
||||
:class="{
|
||||
fixed: fixedHeader,
|
||||
'no-tabs-bar': !showTabs,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="vab-layout-header"
|
||||
:class="{
|
||||
'fixed-header': fixedHeader,
|
||||
}"
|
||||
>
|
||||
<vab-header layout="common" />
|
||||
<div>
|
||||
<vab-side-bar layout="common" />
|
||||
<div
|
||||
v-show="showTabs"
|
||||
class="vab-main"
|
||||
:class="{
|
||||
'is-collapse-main': collapse,
|
||||
}"
|
||||
>
|
||||
<vab-tabs layout="common" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="vab-main main-padding"
|
||||
:class="{
|
||||
'is-collapse-main': collapse,
|
||||
}"
|
||||
>
|
||||
<vab-app-main />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-layout-common {
|
||||
:deep() {
|
||||
.vab-tabs-content {
|
||||
width: calc(
|
||||
100% - 60px - #{$base-font-size-default} -
|
||||
#{$base-padding} - 2px
|
||||
) !important;
|
||||
}
|
||||
|
||||
.vab-header {
|
||||
.vab-main {
|
||||
width: 100%;
|
||||
margin: auto $base-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
58
library/layouts/VabLayoutComprehensive/index.vue
Normal file
58
library/layouts/VabLayoutComprehensive/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<!--综合布局 -->
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
fixedHeader: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
showTabs: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
device: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'desktop'
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vab-layout-comprehensive"
|
||||
:class="{
|
||||
fixed: fixedHeader,
|
||||
'no-tabs-bar': !showTabs,
|
||||
}"
|
||||
>
|
||||
<vab-side-bar layout="comprehensive" />
|
||||
<div
|
||||
class="vab-main"
|
||||
:class="{
|
||||
'is-collapse-main': collapse,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="vab-layout-header"
|
||||
:class="{
|
||||
'fixed-header': fixedHeader,
|
||||
}"
|
||||
>
|
||||
<vab-nav layout="comprehensive" />
|
||||
<vab-tabs v-show="showTabs" />
|
||||
</div>
|
||||
<vab-app-main />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
98
library/layouts/VabLayoutFloat/index.vue
Normal file
98
library/layouts/VabLayoutFloat/index.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<!--浮动布局 -->
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
defineProps({
|
||||
fixedHeader: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
showTabs: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { foldSideBar, openSideBar } = settingsStore
|
||||
|
||||
foldSideBar()
|
||||
onUnmounted(() => {
|
||||
openSideBar()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vab-layout-float"
|
||||
:class="{
|
||||
fixed: fixedHeader,
|
||||
'no-tabs-bar': !showTabs,
|
||||
}"
|
||||
>
|
||||
<vab-side-bar layout="float" />
|
||||
<div class="vab-main">
|
||||
<div
|
||||
class="vab-layout-header"
|
||||
:class="{
|
||||
'fixed-header': fixedHeader,
|
||||
}"
|
||||
>
|
||||
<vab-nav layout="float" />
|
||||
<vab-tabs v-show="showTabs" />
|
||||
</div>
|
||||
<vab-app-main />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!--由于element-plus
|
||||
bug使用teleported=false会导致多级路由无法显示,故所有菜单必须生成至body下,样式必须放到body下-->
|
||||
<style lang="scss" scoped>
|
||||
.vab-layout-float {
|
||||
:deep() {
|
||||
.vab-main {
|
||||
margin-left: $base-left-menu-width-min !important;
|
||||
|
||||
.fixed-header {
|
||||
width: $base-right-content-width-min !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--collapse.el-menu li.el-sub-menu.is-active {
|
||||
.el-sub-menu__title {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
> .el-sub-menu__title {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-menu-children-height {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
&--vertical {
|
||||
.el-menu--popup-right-start {
|
||||
width: 335px !important;
|
||||
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
float: left;
|
||||
width: 160px;
|
||||
min-width: 160px;
|
||||
margin: 0 0 5px 5px;
|
||||
border-radius: $base-border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
83
library/layouts/VabLayoutHorizontal/index.vue
Normal file
83
library/layouts/VabLayoutHorizontal/index.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<!-- 横向布局 -->
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
fixedHeader: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
showTabs: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
device: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'desktop'
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vab-layout-horizontal"
|
||||
:class="{
|
||||
fixed: fixedHeader,
|
||||
'no-tabs-bar': !showTabs,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="vab-layout-header"
|
||||
:class="{
|
||||
'fixed-header': fixedHeader,
|
||||
}"
|
||||
>
|
||||
<vab-header layout="horizontal" />
|
||||
<div
|
||||
v-show="showTabs"
|
||||
:class="{
|
||||
'vab-tabs-horizontal': showTabs,
|
||||
}"
|
||||
>
|
||||
<div class="vab-main">
|
||||
<vab-tabs />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vab-main main-padding">
|
||||
<vab-app-main />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vab-layout-horizontal {
|
||||
:deep() {
|
||||
.vab-main {
|
||||
width: 92% !important;
|
||||
margin: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs-horizontal {
|
||||
background: var(--el-color-white);
|
||||
box-shadow: $base-box-shadow;
|
||||
}
|
||||
|
||||
.vab-nav {
|
||||
.fold-unfold {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
library/layouts/VabLayoutVertical/index.vue
Normal file
68
library/layouts/VabLayoutVertical/index.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<!-- 纵向布局 -->
|
||||
<script lang="ts" setup>
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
defineProps({
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
fixedHeader: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
showTabs: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
},
|
||||
},
|
||||
device: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'desktop'
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { foldSideBar } = settingsStore
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="vab-layout-vertical"
|
||||
:class="{
|
||||
fixed: fixedHeader,
|
||||
'no-tabs-bar': !showTabs,
|
||||
}"
|
||||
>
|
||||
<vab-side-bar />
|
||||
<div
|
||||
v-if="device === 'mobile' && !collapse"
|
||||
class="v-modal"
|
||||
@click="foldSideBar"
|
||||
/>
|
||||
<div
|
||||
class="vab-main"
|
||||
:class="{
|
||||
'is-collapse-main': collapse,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="vab-layout-header"
|
||||
:class="{
|
||||
'fixed-header': fixedHeader,
|
||||
}"
|
||||
>
|
||||
<vab-nav />
|
||||
<vab-tabs v-show="showTabs" />
|
||||
</div>
|
||||
<vab-app-main />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
162
library/layouts/index.vue
Normal file
162
library/layouts/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="vue-admin-better-wrapper" :class="{ mobile }">
|
||||
<component
|
||||
:is="'vab-layout-' + theme.layout"
|
||||
:collapse="collapse"
|
||||
:device="device"
|
||||
:fixed-header="theme.fixedHeader"
|
||||
:show-tabs="theme.showTabs"
|
||||
/>
|
||||
<el-backtop target="#app" />
|
||||
<!-- 主题组件放到layouts下防止主题切换,导致主题组件重新加载 -->
|
||||
<vab-theme-drawer />
|
||||
<vab-theme-setting />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
const imports = require.context('./', true, /\.vue$/)
|
||||
const Components: any = {}
|
||||
imports
|
||||
.keys()
|
||||
.filter((key) => key !== './index.vue')
|
||||
.forEach((key) => {
|
||||
Components[key.replace(/(\/|\.|index.vue)/g, '')] =
|
||||
imports(key).default
|
||||
})
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Layouts',
|
||||
components: Components,
|
||||
setup() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const { device, collapse, theme } = storeToRefs(settingsStore)
|
||||
const { toggleDevice, foldSideBar, openSideBar, updateTheme } =
|
||||
settingsStore
|
||||
|
||||
const mobile = ref(false)
|
||||
let oldLayout = theme.value.layout
|
||||
|
||||
const resizeBody = () => {
|
||||
mobile.value =
|
||||
document.body.getBoundingClientRect().width - 1 < 992
|
||||
}
|
||||
|
||||
watch(mobile, (val) => {
|
||||
if (val) {
|
||||
oldLayout = theme.value.layout
|
||||
foldSideBar()
|
||||
} else openSideBar()
|
||||
|
||||
theme.value.layout = val ? 'vertical' : oldLayout
|
||||
toggleDevice(val ? 'mobile' : 'desktop')
|
||||
})
|
||||
|
||||
resizeBody()
|
||||
updateTheme()
|
||||
|
||||
const cleanup = useEventListener('resize', () => {
|
||||
resizeBody()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (mobile) theme.value.layout = oldLayout
|
||||
cleanup()
|
||||
})
|
||||
|
||||
return {
|
||||
theme,
|
||||
device,
|
||||
mobile,
|
||||
collapse,
|
||||
foldSideBar,
|
||||
openSideBar,
|
||||
toggleDevice,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.vue-admin-better-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
[class*='vab-layout-'] {
|
||||
:deep() {
|
||||
.vab-layout-header {
|
||||
box-shadow: $base-box-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
&.fixed {
|
||||
padding-top: $base-nav-height + $base-tabs-height;
|
||||
}
|
||||
|
||||
&.fixed.no-tabs-bar {
|
||||
padding-top: $base-nav-height;
|
||||
}
|
||||
}
|
||||
|
||||
:deep() {
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: $base-z-index - 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vab-main {
|
||||
position: relative;
|
||||
width: auto;
|
||||
min-height: 100%;
|
||||
margin-left: var(--el-left-menu-width);
|
||||
|
||||
&.is-collapse-main {
|
||||
margin-left: $base-left-menu-width-min;
|
||||
|
||||
.fixed-header {
|
||||
width: $base-right-content-width-min;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-collapse-main) {
|
||||
.fixed-header {
|
||||
width: calc(100% - var(--el-left-menu-width));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端开始 */
|
||||
&.mobile {
|
||||
:deep() {
|
||||
.vab-layout-vertical {
|
||||
.el-scrollbar.vab-side-bar.is-collapse {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.vab-main {
|
||||
.fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 隐藏分页和页码跳转 */
|
||||
.el-pager,
|
||||
.el-pagination__jump {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端结束 */
|
||||
}
|
||||
</style>
|
||||
19
library/plugins/directive.ts
Normal file
19
library/plugins/directive.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { App, DirectiveBinding } from 'vue'
|
||||
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
/**
|
||||
* @description 自定义指令v-permissions
|
||||
*/
|
||||
app.directive('permissions', {
|
||||
mounted(el: any, binding: DirectiveBinding) {
|
||||
const { value } = binding
|
||||
if (value)
|
||||
if (!hasPermission(value))
|
||||
el.parentNode && el.parentNode.removeChild(el)
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
30
library/plugins/errorLog.ts
Normal file
30
library/plugins/errorLog.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { App } from 'vue'
|
||||
import pinia from '@/store'
|
||||
import { useErrorLogStore } from '@/store/modules/errorLog'
|
||||
import { errorLog } from '@/config'
|
||||
import { isArray, isString } from '@/utils/validate'
|
||||
|
||||
export const needErrorLog = () => {
|
||||
const errorLogArray = isArray(errorLog)
|
||||
? [...errorLog]
|
||||
: isString(errorLog)
|
||||
? [...[errorLog]]
|
||||
: []
|
||||
return errorLogArray.includes(process.env.NODE_ENV as string)
|
||||
}
|
||||
|
||||
export const addErrorLog = (err: any) => {
|
||||
// eslint-disable-next-line no-console
|
||||
if (!err.isRequest) console.error('vue-admin-better错误拦截:', err)
|
||||
const url = window.location.href
|
||||
const { addErrorLog } = useErrorLogStore(pinia)
|
||||
addErrorLog({ err, url })
|
||||
}
|
||||
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
if (needErrorLog()) {
|
||||
app.config.errorHandler = addErrorLog
|
||||
}
|
||||
},
|
||||
}
|
||||
26
library/plugins/support.ts
Normal file
26
library/plugins/support.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { App } from 'vue'
|
||||
import pinia from '@/store'
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
const { title } = useSettingsStore(pinia)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
` %c ${title} %c 基于admin-plus ${__APP_INFO__['version']} 构建 `,
|
||||
'color: #fadfa3; background: #030307; padding:5px 0;',
|
||||
'background: #fadfa3; padding:5px 0;'
|
||||
)
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
const str = '\u0076\u0061\u0062\u002d\u0069\u0063\u006f\u006e\u0073'
|
||||
const key = decodeURI(str.replace(/\\u/g, '%u'))
|
||||
if (!__APP_INFO__['dependencies'][key]) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
app.config.globalProperties = null
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
172
library/plugins/vab.ts
Normal file
172
library/plugins/vab.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { App } from 'vue'
|
||||
import mitt from 'mitt'
|
||||
import _ from 'lodash'
|
||||
import { loadingText, messageDuration } from '@/config'
|
||||
import { globalPropertiesType } from '/#/library'
|
||||
|
||||
export let gp: globalPropertiesType
|
||||
|
||||
export default {
|
||||
install(app: App<Element>) {
|
||||
gp = {
|
||||
/**
|
||||
* @description 全局加载层
|
||||
* @param {number} index 自定义加载图标类名ID
|
||||
* @param {string} text 显示在加载图标下方的加载文案
|
||||
*/
|
||||
$baseLoading: (index = undefined, text = loadingText) => {
|
||||
return ElLoading.service({
|
||||
lock: true,
|
||||
text,
|
||||
spinner: index ? `vab-loading-type${index}` : index,
|
||||
background: 'hsla(0,0%,100%,.8)',
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 全局Message
|
||||
* @param {string} message 消息文字
|
||||
* @param {'success'|'warning'|'info'|'error'} type 主题
|
||||
* @param {string} customClass 自定义类名
|
||||
* @param {boolean} dangerouslyUseHTMLString 是否将message属性作为HTML片段处理
|
||||
*/
|
||||
$baseMessage: (
|
||||
message,
|
||||
type = 'info',
|
||||
customClass,
|
||||
dangerouslyUseHTMLString
|
||||
) => {
|
||||
ElMessage({
|
||||
message,
|
||||
type,
|
||||
customClass,
|
||||
duration: messageDuration,
|
||||
dangerouslyUseHTMLString,
|
||||
showClose: true,
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 全局Alert
|
||||
* @param {string|VNode} content 消息正文内容
|
||||
* @param {string} title 标题
|
||||
* @param {function} callback 若不使用Promise,可以使用此参数指定MessageBox关闭后的回调
|
||||
*/
|
||||
$baseAlert: (content, title = '温馨提示', callback = undefined) => {
|
||||
if (title && typeof title == 'function') {
|
||||
callback = title
|
||||
title = '温馨提示'
|
||||
}
|
||||
ElMessageBox.alert(content, title, {
|
||||
confirmButtonText: '确定',
|
||||
dangerouslyUseHTMLString: true, // 此处可能引起跨站攻击,建议配置为false
|
||||
callback: () => {
|
||||
if (callback) callback()
|
||||
},
|
||||
}).then(() => {})
|
||||
},
|
||||
/**
|
||||
* @description 全局Confirm
|
||||
* @param {string|VNode} content 消息正文内容
|
||||
* @param {string} title 标题
|
||||
* @param {function} callback1 确认回调
|
||||
* @param {function} callback2 关闭或取消回调
|
||||
* @param {string} confirmButtonText 确定按钮的文本内容
|
||||
* @param {string} cancelButtonText 取消按钮的自定义类名
|
||||
*/
|
||||
$baseConfirm: (
|
||||
content,
|
||||
title,
|
||||
callback1,
|
||||
callback2,
|
||||
confirmButtonText = '确定',
|
||||
cancelButtonText = '取消'
|
||||
) => {
|
||||
ElMessageBox.confirm(content, title || '温馨提示', {
|
||||
confirmButtonText,
|
||||
cancelButtonText,
|
||||
closeOnClickModal: false,
|
||||
type: 'warning',
|
||||
lockScroll: false,
|
||||
})
|
||||
.then(() => {
|
||||
if (callback1) {
|
||||
callback1()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (callback2) {
|
||||
callback2()
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 全局Notification
|
||||
* @param {string} message 说明文字
|
||||
* @param {string} title 标题
|
||||
* @param {'success'|'warning'|'info'|'error'} type 主题样式,如果不在可选值内将被忽略
|
||||
* @param {'top-right'|'top-left'|'bottom-right'|'bottom-left'} position 自定义弹出位置
|
||||
* @param duration 显示时间,毫秒
|
||||
*/
|
||||
$baseNotify: (
|
||||
message,
|
||||
title,
|
||||
type = 'success',
|
||||
position = 'top-right',
|
||||
duration = messageDuration
|
||||
) => {
|
||||
ElNotification({
|
||||
title,
|
||||
message,
|
||||
type,
|
||||
duration,
|
||||
position,
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 表格高度
|
||||
* @param {*} formType
|
||||
*/
|
||||
$baseTableHeight: (formType) => {
|
||||
let height = window.innerHeight
|
||||
const paddingHeight = 291
|
||||
const formHeight = 60
|
||||
|
||||
if ('number' === typeof formType) {
|
||||
height = height - paddingHeight - formHeight * formType
|
||||
} else {
|
||||
height = height - paddingHeight
|
||||
}
|
||||
return height
|
||||
},
|
||||
$pub: (...args: any[]) => {
|
||||
_emitter.emit(_.head(args), args[1])
|
||||
},
|
||||
$sub: function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
Reflect.apply(_emitter.on, _emitter, _.toArray(arguments))
|
||||
},
|
||||
$unsub: function () {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
Reflect.apply(_emitter.off, _emitter, _.toArray(arguments))
|
||||
},
|
||||
}
|
||||
|
||||
const _emitter = mitt()
|
||||
Object.keys(gp).forEach((key) => {
|
||||
app.provide(key, gp[key as keyof typeof gp])
|
||||
// 允许vue3下继续使用vue2中的this调用vab方法
|
||||
app.config.globalProperties[key] = gp[key as keyof typeof gp]
|
||||
})
|
||||
|
||||
if (process.env['NODE_' + 'ENV'] !== `${'deve' + 'lopme' + 'nt'}`) {
|
||||
const key = 'vab-' + 'icons'
|
||||
if (!__APP_INFO__['dependencies'][key]) {
|
||||
// @ts-ignore
|
||||
app.config.globalProperties = null
|
||||
}
|
||||
if (!process.env['VUE_' + 'APP_' + 'SECRET_' + 'KEY']) {
|
||||
// @ts-ignore
|
||||
app.config.globalProperties = null
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
12
library/shims-vab.d.ts
vendored
Normal file
12
library/shims-vab.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
declare let __APP_INFO__: any
|
||||
|
||||
// CSS
|
||||
type CSSModuleClasses = { readonly [key: string]: string }
|
||||
declare module '*.module.scss' {
|
||||
const classes: CSSModuleClasses
|
||||
export default classes
|
||||
}
|
||||
declare module '*.scss' {
|
||||
const css: string
|
||||
export default css
|
||||
}
|
||||
251
library/styles/background/black.scss
Normal file
251
library/styles/background/black.scss
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* @description 黑
|
||||
*/
|
||||
|
||||
body.vab-theme-black {
|
||||
$base-menu-background: #282c34;
|
||||
|
||||
@mixin container {
|
||||
color: var(--el-color-white) !important;
|
||||
background: $base-menu-background !important;
|
||||
}
|
||||
|
||||
@mixin active {
|
||||
&:hover {
|
||||
color: var(--el-color-white) !important;
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-white) !important;
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container-vertical,
|
||||
.logo-container-horizontal,
|
||||
.logo-container-comprehensive,
|
||||
.logo-container-float {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.logo-container-column {
|
||||
.logo {
|
||||
@include container;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-bar-container.el-scrollbar {
|
||||
.el-tabs {
|
||||
.el-tabs__nav-wrap.is-left {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.el-tabs__item.is-active {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu__title.is-active,
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
i {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-bar-container-card.el-scrollbar {
|
||||
.el-tabs {
|
||||
.el-tabs__item.is-active {
|
||||
background: transparent !important;
|
||||
|
||||
.vab-column-grid {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-bar-container-arrow.el-scrollbar {
|
||||
.el-tabs {
|
||||
.el-tabs__item.is-active {
|
||||
background: transparent !important;
|
||||
|
||||
.vab-column-grid {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-float,
|
||||
.vab-layout-common,
|
||||
.vab-layout-vertical,
|
||||
.vab-layout-horizontal,
|
||||
.vab-layout-comprehensive {
|
||||
.el-menu {
|
||||
@include container;
|
||||
|
||||
.el-sub-menu .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
@include container;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-side-bar,
|
||||
.comprehensive-bar-container {
|
||||
@include container;
|
||||
|
||||
.el-menu-item {
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-float {
|
||||
.el-scrollbar__view
|
||||
.el-menu--collapse.el-menu
|
||||
li.el-sub-menu.is-active {
|
||||
.el-sub-menu__title {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
> .el-sub-menu__title {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-header {
|
||||
@include container;
|
||||
|
||||
.vab-main {
|
||||
@include container;
|
||||
|
||||
.right-panel {
|
||||
.el-menu {
|
||||
&--horizontal {
|
||||
.el-sub-menu .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
}
|
||||
[role='menubar'].el-menu--horizontal {
|
||||
> .el-sub-menu.is-active[tabindex='0'] {
|
||||
> .el-sub-menu__title {
|
||||
color: var(--el-color-white) !important;
|
||||
background-color: var(
|
||||
--el-color-primary
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs {
|
||||
&-more {
|
||||
&-active,
|
||||
&:hover {
|
||||
.vab-tabs-more-icon {
|
||||
.box:before,
|
||||
.box:after {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs-content-card {
|
||||
.el-tabs__header {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
border: 1px solid var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs-content-smart {
|
||||
.el-tabs__header {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
|
||||
&:after {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs-content-smooth {
|
||||
.el-tabs__header {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(
|
||||
--el-color-primary-light-9
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-black) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-nav {
|
||||
.el-tabs__item.is-active,
|
||||
.el-tabs__item:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.el-tabs__active-bar {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
#nprogress {
|
||||
.bar {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.peg {
|
||||
box-shadow:
|
||||
0 0 10px var(--el-color-primary),
|
||||
0 0 5px var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
108
library/styles/background/image.scss
Normal file
108
library/styles/background/image.scss
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @description 菜单背景
|
||||
*/
|
||||
|
||||
body.vab-background > #app {
|
||||
$base-menu-background: url('~@/assets/theme_images/background-1.png')
|
||||
no-repeat;
|
||||
|
||||
@mixin container {
|
||||
color: $base-color-white !important;
|
||||
background: $base-menu-background !important;
|
||||
background-size: auto 100% !important;
|
||||
}
|
||||
@mixin transparent {
|
||||
color: $base-color-white !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
@mixin active {
|
||||
span {
|
||||
color: $base-color-white !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $base-color-white !important;
|
||||
background-color: rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $base-color-white !important;
|
||||
background-color: rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-side-bar:not(.is-collapse),
|
||||
.comprehensive-bar-container {
|
||||
@include container;
|
||||
|
||||
.el-menu {
|
||||
@include transparent;
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
@include transparent;
|
||||
@include active;
|
||||
|
||||
i,
|
||||
svg {
|
||||
@include transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container-vertical,
|
||||
.logo-container-comprehensive,
|
||||
.logo-container-float {
|
||||
@include transparent;
|
||||
|
||||
.logo .vab-icon,
|
||||
.title {
|
||||
@include transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-bar-container {
|
||||
&.el-scrollbar {
|
||||
.logo-container-column {
|
||||
.logo {
|
||||
@include container;
|
||||
background: #034291 !important;
|
||||
|
||||
.vab-icon {
|
||||
@include transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-tabs {
|
||||
.el-tabs__nav-wrap.is-left {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.el-tabs__nav,
|
||||
.el-tabs__item {
|
||||
@include transparent;
|
||||
|
||||
&.is-active {
|
||||
color: $base-color-white !important;
|
||||
background-color: rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.vab-column-bar-container-card {
|
||||
.el-tabs {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
background: transparent !important;
|
||||
.vab-column-grid {
|
||||
background-color: rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
181
library/styles/background/ocean.scss
Normal file
181
library/styles/background/ocean.scss
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @description 渐变
|
||||
*/
|
||||
|
||||
body.vab-theme-ocean {
|
||||
$base-color-blue: #1890ff;
|
||||
$base-color-blue-active: #399efd;
|
||||
|
||||
@mixin container {
|
||||
background: linear-gradient(to right, #006cff, #399efd) !important;
|
||||
}
|
||||
|
||||
@mixin active {
|
||||
&:hover {
|
||||
color: $base-color-white;
|
||||
background-color: $base-color-blue-active !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $base-color-white;
|
||||
background-color: $base-color-blue-active !important;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container-horizontal {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.logo-container-vertical,
|
||||
.logo-container-comprehensive,
|
||||
.logo-container-float {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.logo-container-column {
|
||||
.logo {
|
||||
@include container;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-bar-container {
|
||||
.el-tabs {
|
||||
.el-tabs__nav-wrap.is-left {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.el-tabs__nav {
|
||||
@include container;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu__title.is-active,
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
i {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-card {
|
||||
.el-tabs {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-horizontal {
|
||||
.vab-header {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
background: var(--el-color-primary) !important;
|
||||
|
||||
.el-sub-menu__title {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-side-bar,
|
||||
.comprehensive-bar-container {
|
||||
background: var(--el-color-primary) !important;
|
||||
|
||||
.el-menu-item {
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-vertical,
|
||||
.vab-layout-comprehensive,
|
||||
.vab-layout-common,
|
||||
.vab-layout-float {
|
||||
.vab-side-bar,
|
||||
.comprehensive-bar-container {
|
||||
@include container;
|
||||
|
||||
.el-menu {
|
||||
@include container;
|
||||
@include active;
|
||||
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
background-color: transparent !important;
|
||||
@include active;
|
||||
|
||||
&.is-active {
|
||||
background-color: $base-color-blue-active !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-float {
|
||||
.el-scrollbar__view
|
||||
.el-menu--collapse.el-menu
|
||||
li.el-sub-menu.is-active {
|
||||
.el-sub-menu__title {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
> .el-sub-menu__title {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-header {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
|
||||
.vab-main {
|
||||
.el-menu.el-menu {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
|
||||
&--horizontal {
|
||||
.el-sub-menu,
|
||||
.el-menu-item {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
|
||||
&.is-active {
|
||||
color: $base-color-white !important;
|
||||
background-color: $base-color-blue-active !important;
|
||||
}
|
||||
}
|
||||
|
||||
> .el-menu-item,
|
||||
.el-sub-menu__title,
|
||||
> .el-menu-item:hover,
|
||||
> .el-sub-menu__title:hover {
|
||||
color: $base-color-white !important;
|
||||
background-color: var(--el-color-primary) !important;
|
||||
|
||||
i {
|
||||
color: $base-color-white !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $base-color-white !important;
|
||||
background-color: $base-color-blue-active !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
293
library/styles/background/white.scss
Normal file
293
library/styles/background/white.scss
Normal file
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* @description 白
|
||||
*/
|
||||
|
||||
body.vab-theme-white {
|
||||
$base-menu-background: #fff;
|
||||
|
||||
@mixin container {
|
||||
color: #515a6e !important;
|
||||
background: $base-menu-background !important;
|
||||
}
|
||||
|
||||
@mixin active {
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
|
||||
i,
|
||||
svg,
|
||||
span[title] {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
|
||||
i,
|
||||
svg,
|
||||
span[title] {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container-common,
|
||||
.logo-container-vertical,
|
||||
.logo-container-horizontal,
|
||||
.logo-container-comprehensive,
|
||||
.logo-container-float {
|
||||
@include container;
|
||||
|
||||
.title,
|
||||
.vab-icon {
|
||||
@include container;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container-column {
|
||||
@include container;
|
||||
|
||||
.title {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.logo,
|
||||
.vab-icon {
|
||||
@include container;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-column-bar-container {
|
||||
.el-tabs {
|
||||
@include container;
|
||||
|
||||
.el-tabs__nav-wrap.is-left {
|
||||
background: #f7faff !important;
|
||||
}
|
||||
|
||||
.el-tabs__item,
|
||||
.el-tabs__nav {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.el-tabs__item.is-active {
|
||||
color: var(--el-color-white) !important;
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu__title.is-active,
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
i {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-card {
|
||||
.el-tabs {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
.el-tabs {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
color: var(--el-color-black) !important;
|
||||
background: transparent !important;
|
||||
|
||||
.vab-column-grid {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-float,
|
||||
.vab-layout-common,
|
||||
.vab-layout-vertical,
|
||||
.vab-layout-horizontal,
|
||||
.vab-layout-comprehensive {
|
||||
.el-menu {
|
||||
@include container;
|
||||
|
||||
.el-sub-menu .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active,
|
||||
.el-sub-menu__title.is-active,
|
||||
.el-menu-item:hover,
|
||||
.el-sub-menu__title:hover {
|
||||
i {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vab-side-bar,
|
||||
.comprehensive-bar-container {
|
||||
@include container;
|
||||
|
||||
.el-menu-item {
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-float {
|
||||
.el-scrollbar__view
|
||||
.el-menu--collapse.el-menu
|
||||
li.el-sub-menu.is-active {
|
||||
.el-sub-menu__title {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
> .el-sub-menu__title {
|
||||
color: var(--el-color-primary) !important;
|
||||
background-color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-header {
|
||||
@include container;
|
||||
|
||||
.vab-main {
|
||||
@include container;
|
||||
|
||||
.right-panel {
|
||||
.user-name,
|
||||
.user-name *,
|
||||
> i,
|
||||
> div > i,
|
||||
> span > i,
|
||||
> div > span > i,
|
||||
> svg,
|
||||
> div > svg,
|
||||
> span > svg,
|
||||
> div > span > svg,
|
||||
.ri-notification-line,
|
||||
.ri-translate,
|
||||
.ri-bug-line {
|
||||
@include container;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
&--horizontal {
|
||||
.el-sub-menu .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
@include active;
|
||||
}
|
||||
|
||||
.el-sub-menu,
|
||||
.el-menu-item {
|
||||
&.is-active {
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
|
||||
> .el-sub-menu.is-active {
|
||||
> .el-sub-menu__title {
|
||||
background-color: var(
|
||||
--el-color-primary-light-9
|
||||
) !important;
|
||||
@include active;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs {
|
||||
&-more {
|
||||
&-active,
|
||||
&:hover {
|
||||
.vab-tabs-more-icon {
|
||||
.box:before,
|
||||
.box:after {
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs-content-card {
|
||||
.el-tabs__header {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
border: 1px solid var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs-content-smart {
|
||||
.el-tabs__header {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
|
||||
&:after {
|
||||
background-color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vab-tabs-content-smooth {
|
||||
.el-tabs__header {
|
||||
.el-tabs__item {
|
||||
&.is-active {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(
|
||||
--el-color-primary-light-9
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-black) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
library/styles/loading/dots.css
Normal file
124
library/styles/loading/dots.css
Normal file
@@ -0,0 +1,124 @@
|
||||
.dots-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: transparent;
|
||||
border-radius: 100%;
|
||||
box-shadow:
|
||||
#f86 -14px -14px 0 7px,
|
||||
#fc6 14px -14px 0 7px,
|
||||
#6d7 14px 14px 0 7px,
|
||||
#4ae -14px 14px 0 7px;
|
||||
transform-origin: 50% 50%;
|
||||
animation: dots-loader 5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes dots-loader {
|
||||
0% {
|
||||
box-shadow:
|
||||
#f86 -14px -14px 0 7px,
|
||||
#fc6 14px -14px 0 7px,
|
||||
#6d7 14px 14px 0 7px,
|
||||
#4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
8.33% {
|
||||
box-shadow:
|
||||
#f86 14px -14px 0 7px,
|
||||
#fc6 14px -14px 0 7px,
|
||||
#6d7 14px 14px 0 7px,
|
||||
#4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
16.67% {
|
||||
box-shadow:
|
||||
#f86 14px 14px 0 7px,
|
||||
#fc6 14px 14px 0 7px,
|
||||
#6d7 14px 14px 0 7px,
|
||||
#4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
25% {
|
||||
box-shadow:
|
||||
#f86 -14px 14px 0 7px,
|
||||
#fc6 -14px 14px 0 7px,
|
||||
#6d7 -14px 14px 0 7px,
|
||||
#4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
33.33% {
|
||||
box-shadow:
|
||||
#f86 -14px -14px 0 7px,
|
||||
#fc6 -14px 14px 0 7px,
|
||||
#6d7 -14px -14px 0 7px,
|
||||
#4ae -14px -14px 0 7px;
|
||||
}
|
||||
|
||||
41.67% {
|
||||
box-shadow:
|
||||
#f86 14px -14px 0 7px,
|
||||
#fc6 -14px 14px 0 7px,
|
||||
#6d7 -14px -14px 0 7px,
|
||||
#4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow:
|
||||
#f86 14px 14px 0 7px,
|
||||
#fc6 -14px 14px 0 7px,
|
||||
#6d7 -14px -14px 0 7px,
|
||||
#4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
58.33% {
|
||||
box-shadow:
|
||||
#f86 -14px 14px 0 7px,
|
||||
#fc6 -14px 14px 0 7px,
|
||||
#6d7 -14px -14px 0 7px,
|
||||
#4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
66.67% {
|
||||
box-shadow:
|
||||
#f86 -14px -14px 0 7px,
|
||||
#fc6 -14px -14px 0 7px,
|
||||
#6d7 -14px -14px 0 7px,
|
||||
#4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
75% {
|
||||
box-shadow:
|
||||
#f86 14px -14px 0 7px,
|
||||
#fc6 14px -14px 0 7px,
|
||||
#6d7 14px -14px 0 7px,
|
||||
#4ae 14px -14px 0 7px;
|
||||
}
|
||||
|
||||
83.33% {
|
||||
box-shadow:
|
||||
#f86 14px 14px 0 7px,
|
||||
#fc6 14px -14px 0 7px,
|
||||
#6d7 14px 14px 0 7px,
|
||||
#4ae 14px 14px 0 7px;
|
||||
}
|
||||
|
||||
91.67% {
|
||||
box-shadow:
|
||||
#f86 -14px 14px 0 7px,
|
||||
#fc6 14px -14px 0 7px,
|
||||
#6d7 14px 14px 0 7px,
|
||||
#4ae -14px 14px 0 7px;
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow:
|
||||
#f86 -14px -14px 0 7px,
|
||||
#fc6 14px -14px 0 7px,
|
||||
#6d7 14px 14px 0 7px,
|
||||
#4ae -14px 14px 0 7px;
|
||||
}
|
||||
}
|
||||
104
library/styles/loading/gauge.css
Normal file
104
library/styles/loading/gauge.css
Normal file
@@ -0,0 +1,104 @@
|
||||
.gauge-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 64px;
|
||||
height: 32px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: #6ca;
|
||||
border-top-left-radius: 32px;
|
||||
border-top-right-radius: 32px;
|
||||
}
|
||||
|
||||
.gauge-loader:not(:required)::before {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 30px;
|
||||
width: 4px;
|
||||
height: 27px;
|
||||
content: '';
|
||||
background: white;
|
||||
border-radius: 2px;
|
||||
transform-origin: 50% 100%;
|
||||
animation: gauge-loader 4000ms infinite ease;
|
||||
}
|
||||
|
||||
.gauge-loader:not(:required)::after {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
left: 26px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
content: '';
|
||||
background: white;
|
||||
-moz-border-radius: 8px;
|
||||
-webkit-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes gauge-loader {
|
||||
0% {
|
||||
transform: rotate(-50deg);
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
24% {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: rotate(-20deg);
|
||||
}
|
||||
|
||||
54% {
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
56% {
|
||||
transform: rotate(78deg);
|
||||
}
|
||||
|
||||
58% {
|
||||
transform: rotate(73deg);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: rotate(75deg);
|
||||
}
|
||||
|
||||
62% {
|
||||
transform: rotate(70deg);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: rotate(-20deg);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
83% {
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
|
||||
86% {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
89% {
|
||||
transform: rotate(25deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(-50deg);
|
||||
}
|
||||
}
|
||||
51
library/styles/loading/inner-circles.css
Normal file
51
library/styles/loading/inner-circles.css
Normal file
@@ -0,0 +1,51 @@
|
||||
.inner-circles-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: rgba(25, 165, 152, 0.5);
|
||||
border-radius: 50%;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.inner-circles-loader:not(:required)::before,
|
||||
.inner-circles-loader:not(:required)::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.inner-circles-loader:not(:required)::before {
|
||||
left: 0;
|
||||
background: #c7efcf;
|
||||
transform-origin: 0 50%;
|
||||
animation: inner-circles-loader 3s infinite;
|
||||
}
|
||||
|
||||
.inner-circles-loader:not(:required)::after {
|
||||
right: 0;
|
||||
background: #eef5db;
|
||||
transform-origin: 100% 50%;
|
||||
animation: inner-circles-loader 3s 0.2s reverse infinite;
|
||||
}
|
||||
|
||||
@keyframes inner-circles-loader {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
341
library/styles/loading/plus.css
Normal file
341
library/styles/loading/plus.css
Normal file
@@ -0,0 +1,341 @@
|
||||
.plus-loader:not(:required) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
background: #f86;
|
||||
-moz-border-radius: 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px;
|
||||
-moz-transform: rotateZ(90deg);
|
||||
-ms-transform: rotateZ(90deg);
|
||||
-webkit-transform: rotateZ(90deg);
|
||||
transform: rotateZ(90deg);
|
||||
-moz-transform-origin: 50% 50%;
|
||||
-ms-transform-origin: 50% 50%;
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
-moz-animation: plus-loader-background 3s infinite ease-in-out;
|
||||
-webkit-animation: plus-loader-background 3s infinite ease-in-out;
|
||||
animation: plus-loader-background 3s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.plus-loader:not(:required)::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 50%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: #f86;
|
||||
-moz-border-radius: 24px 0 0 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px 0 0 24px;
|
||||
-moz-transform-origin: 100% 50%;
|
||||
-ms-transform-origin: 100% 50%;
|
||||
-webkit-transform-origin: 100% 50%;
|
||||
transform-origin: 100% 50%;
|
||||
-moz-animation: plus-loader-top 3s infinite linear;
|
||||
-webkit-animation: plus-loader-top 3s infinite linear;
|
||||
animation: plus-loader-top 3s infinite linear;
|
||||
}
|
||||
|
||||
.plus-loader:not(:required)::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 50%;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
background: #fc6;
|
||||
-moz-border-radius: 24px 0 0 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px 0 0 24px;
|
||||
-moz-transform-origin: 100% 50%;
|
||||
-ms-transform-origin: 100% 50%;
|
||||
-webkit-transform-origin: 100% 50%;
|
||||
transform-origin: 100% 50%;
|
||||
-moz-animation: plus-loader-bottom 3s infinite linear;
|
||||
-webkit-animation: plus-loader-bottom 3s infinite linear;
|
||||
animation: plus-loader-bottom 3s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes plus-loader-top {
|
||||
2.5% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
13.75% {
|
||||
background: #ff430d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
13.76% {
|
||||
background: #ffae0d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-out;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
25% {
|
||||
background: #fc6;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
27.5% {
|
||||
background: #fc6;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
41.25% {
|
||||
background: #ffae0d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
41.26% {
|
||||
background: #2cc642;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-out;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
|
||||
52.5% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
63.75% {
|
||||
background: #2cc642;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
63.76% {
|
||||
background: #1386d2;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-out;
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
|
||||
75% {
|
||||
background: #4ae;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
77.5% {
|
||||
background: #4ae;
|
||||
-moz-transform: rotateY(180deg);
|
||||
-ms-transform: rotateY(180deg);
|
||||
-webkit-transform: rotateY(180deg);
|
||||
transform: rotateY(180deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
91.25% {
|
||||
background: #1386d2;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
91.26% {
|
||||
background: #ff430d;
|
||||
-moz-transform: rotateY(90deg);
|
||||
-ms-transform: rotateY(90deg);
|
||||
-webkit-transform: rotateY(90deg);
|
||||
transform: rotateY(90deg);
|
||||
-moz-animation-timing-function: ease-in;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateY(0deg);
|
||||
-ms-transform: rotateY(0deg);
|
||||
-webkit-transform: rotateY(0deg);
|
||||
transform: rotateY(0deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes plus-loader-bottom {
|
||||
0% {
|
||||
background: #fc6;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #fc6;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
75% {
|
||||
background: #4ae;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #4ae;
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes plus-loader-background {
|
||||
0% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(180deg);
|
||||
-ms-transform: rotateZ(180deg);
|
||||
-webkit-transform: rotateZ(180deg);
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(180deg);
|
||||
-ms-transform: rotateZ(180deg);
|
||||
-webkit-transform: rotateZ(180deg);
|
||||
transform: rotateZ(180deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
27.5% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(90deg);
|
||||
-ms-transform: rotateZ(90deg);
|
||||
-webkit-transform: rotateZ(90deg);
|
||||
transform: rotateZ(90deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(90deg);
|
||||
-ms-transform: rotateZ(90deg);
|
||||
-webkit-transform: rotateZ(90deg);
|
||||
transform: rotateZ(90deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
52.5% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(0deg);
|
||||
-ms-transform: rotateZ(0deg);
|
||||
-webkit-transform: rotateZ(0deg);
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
background: #6d7;
|
||||
-moz-transform: rotateZ(0deg);
|
||||
-ms-transform: rotateZ(0deg);
|
||||
-webkit-transform: rotateZ(0deg);
|
||||
transform: rotateZ(0deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
|
||||
77.5% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(270deg);
|
||||
-ms-transform: rotateZ(270deg);
|
||||
-webkit-transform: rotateZ(270deg);
|
||||
transform: rotateZ(270deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
background: #f86;
|
||||
-moz-transform: rotateZ(270deg);
|
||||
-ms-transform: rotateZ(270deg);
|
||||
-webkit-transform: rotateZ(270deg);
|
||||
transform: rotateZ(270deg);
|
||||
-moz-animation-timing-function: step-start;
|
||||
-webkit-animation-timing-function: step-start;
|
||||
animation-timing-function: step-start;
|
||||
}
|
||||
}
|
||||
377
library/styles/normalize.scss
vendored
Normal file
377
library/styles/normalize.scss
vendored
Normal file
@@ -0,0 +1,377 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
margin: 0.67em 0;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
/* 1 */
|
||||
height: 0;
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
text-decoration: underline dotted;
|
||||
/* 2 */
|
||||
border-bottom: none;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type='button']::-moz-focus-inner,
|
||||
[type='reset']::-moz-focus-inner,
|
||||
[type='submit']::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type='button']:-moz-focusring,
|
||||
[type='reset']:-moz-focusring,
|
||||
[type='submit']:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
display: table;
|
||||
/* 1 */
|
||||
max-width: 100%;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
white-space: normal;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type='checkbox'],
|
||||
[type='radio'] {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type='number']::-webkit-inner-spin-button,
|
||||
[type='number']::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield;
|
||||
/* 1 */
|
||||
outline-offset: -2px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type='search']::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
/* 1 */
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
39
library/styles/transition.scss
Normal file
39
library/styles/transition.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @description vue过渡动画
|
||||
*/
|
||||
|
||||
.fade-transform {
|
||||
&-leave-active,
|
||||
&-enter-active {
|
||||
transition: $base-transition;
|
||||
}
|
||||
|
||||
&-enter,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.no-transform {
|
||||
&-leave-active,
|
||||
&-enter-active {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&-enter,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 旋转动画
|
||||
*/
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
633
library/styles/vab.scss
Normal file
633
library/styles/vab.scss
Normal file
@@ -0,0 +1,633 @@
|
||||
/**
|
||||
* @description 全局样式
|
||||
*/
|
||||
@import 'element-plus/theme-chalk/display.css';
|
||||
@import './normalize';
|
||||
@import './transition';
|
||||
|
||||
@mixin base-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: mix($base-color-white, $base-menu-background, 90%);
|
||||
border: 3px solid transparent;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: mix($base-color-white, $base-menu-background, 80%);
|
||||
}
|
||||
}
|
||||
|
||||
.vab-layout-header,
|
||||
[class*='-bar-container'] {
|
||||
transition: $base-transition;
|
||||
|
||||
* {
|
||||
transition: $base-transition;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
|
||||
body,
|
||||
body[class*='vab-theme-'] {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-family: 'PingFang SC', Arial, 'Microsoft YaHei', sans-serif;
|
||||
font-size: $base-font-size-default;
|
||||
color: var(--el-color-black);
|
||||
background: $base-color-background;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
@include base-scrollbar;
|
||||
|
||||
.vab-main {
|
||||
transition: $base-transition;
|
||||
|
||||
.vab-app-main {
|
||||
width: 100%;
|
||||
padding: $base-padding;
|
||||
overflow: hidden;
|
||||
transition: $base-transition;
|
||||
|
||||
>section {
|
||||
background: var(--el-color-white);
|
||||
transition: $base-transition;
|
||||
|
||||
>[class*='-container'] {
|
||||
min-height: $base-keep-alive-height;
|
||||
padding: $base-padding;
|
||||
background: var(--el-color-white);
|
||||
transition: $base-transition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
outline: none !important;
|
||||
@include base-scrollbar;
|
||||
}
|
||||
|
||||
[class*='ri-'] {
|
||||
vertical-align: -3px !important;
|
||||
}
|
||||
|
||||
.vab-icon {
|
||||
margin: 0 3px 0 0 !important;
|
||||
}
|
||||
|
||||
/*a标签 */
|
||||
a {
|
||||
color: var(--el-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*图片 */
|
||||
img {
|
||||
object-fit: cover;
|
||||
|
||||
&[src=''],
|
||||
&:not([src]) {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* vab-fullscreen全屏 */
|
||||
.vab-fullscreen {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
z-index: $base-z-index + 3 !important;
|
||||
box-sizing: border-box !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
padding-bottom: 15px !important;
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
/* vab-dropdown下拉动画 */
|
||||
.vab-dropdown {
|
||||
transition: $base-transition;
|
||||
|
||||
&-active {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* vab-dot圆点动画 */
|
||||
.vab-dot {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-right: 3px;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
animation: vabDot 1.2s ease-in-out infinite;
|
||||
|
||||
@keyframes vabDot {
|
||||
0% {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(2.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-success {
|
||||
background: var(--el-color-success);
|
||||
|
||||
span {
|
||||
background: var(--el-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
background: var(--el-color-error);
|
||||
|
||||
span {
|
||||
background: var(--el-color-error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vab-data-empty占位图 */
|
||||
.vab-data-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* el-descriptions */
|
||||
.el-descriptions {
|
||||
&__title {
|
||||
padding-left: 10px;
|
||||
border-left: 5px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
/* el-button按钮 */
|
||||
.el-button {
|
||||
border-radius: var(--el-border-radius-base);
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&.is-disabled {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
&.is-round {
|
||||
border-radius: var(--el-border-radius-round);
|
||||
}
|
||||
|
||||
&.is-circle {
|
||||
border-radius: var(--el-border-radius-circle);
|
||||
}
|
||||
|
||||
[class*='el-icon-']+span,
|
||||
span+[class*='el-icon-'],
|
||||
[class*='ri-']+span,
|
||||
span+[class*='ri-'] {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/* el-tag */
|
||||
.el-tag {
|
||||
border-radius: var(--el-border-radius-base);
|
||||
|
||||
&+.el-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&--light:not(&--success, &--info, &--warning, &--danger) {
|
||||
--el-tag-bg-color: var(--el-color-primary-light-9);
|
||||
--el-tag-border-color: var(--el-color-primary-light-8);
|
||||
--el-tag-text-color: var(--el-color-primary);
|
||||
--el-tag-hover-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&--dark:not(&--success, &--info, &--warning, &--danger) {
|
||||
--el-tag-bg-color: var(--el-color-primary);
|
||||
--el-tag-border-color: var(--el-color-primary);
|
||||
--el-tag-hover-color: var(--el-color-primary-2);
|
||||
}
|
||||
|
||||
&.is-round {
|
||||
border-radius: var(--el-border-radius-round);
|
||||
}
|
||||
}
|
||||
|
||||
/* .el-select-tags */
|
||||
.el-select-tags-wrapper {
|
||||
.el-tag.el-tag {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
/* el-select */
|
||||
.el-select {
|
||||
min-width: 115px;
|
||||
}
|
||||
|
||||
a+a,
|
||||
/* span + span, */
|
||||
a+.el-button,
|
||||
.el-button+a {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.el-drawer__wrapper {
|
||||
outline: none !important;
|
||||
|
||||
* {
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* el-overlay遮罩 */
|
||||
.el-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
/* el-image-viewer遮罩 */
|
||||
.el-image-viewer__mask {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
/* v-modal遮罩 */
|
||||
.v-modal {
|
||||
z-index: $base-z-index;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0.6;
|
||||
//backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* el-loading-mask遮罩 */
|
||||
.el-loading-mask {
|
||||
z-index: $base-z-index - 10 !important;
|
||||
|
||||
&.is-fullscreen {
|
||||
z-index: $base-z-index + 99 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* el-scrollbar滚动条 */
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
|
||||
&__bar {
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
&__thumb {
|
||||
background-color: mix($base-color-white,
|
||||
$base-menu-background,
|
||||
90%);
|
||||
|
||||
&:hover {
|
||||
background-color: mix($base-color-white,
|
||||
$base-menu-background,
|
||||
80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* el-form表单 */
|
||||
.el-form--label-top {
|
||||
.el-form-item__label {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.el-range-editor--small {
|
||||
|
||||
.el-range__icon,
|
||||
.el-range__close-icon {
|
||||
line-height: 23.5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* el-badge */
|
||||
.el-badge__content {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* .el-page-header */
|
||||
.el-page-header {
|
||||
margin: 0 0 $base-margin 0;
|
||||
}
|
||||
|
||||
/* el-alert */
|
||||
.el-alert {
|
||||
margin: 0 0 $base-margin 0;
|
||||
|
||||
&__closebtn {
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
&--success.is-light {
|
||||
color: var(--el-color-success);
|
||||
background-color: var(--el-color-success-lighter);
|
||||
border: 1px solid var(--el-color-success);
|
||||
|
||||
i {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
&--info.is-light {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary);
|
||||
|
||||
i {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&--warning.is-light {
|
||||
color: var(--el-color-warning);
|
||||
background-color: var(--el-color-warning-lighter);
|
||||
border: 1px solid var(--el-color-warning);
|
||||
|
||||
i {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
&--error.is-light {
|
||||
color: var(--el-color-error);
|
||||
background-color: var(--el-color-error-lighter);
|
||||
border: 1px solid var(--el-color-error);
|
||||
|
||||
i {
|
||||
color: var(--el-color-error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* el-divider间隔线 */
|
||||
.el-divider--horizontal {
|
||||
margin: 8px 0 $base-margin + 8px 0;
|
||||
|
||||
.el-divider__text {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
/* nprogress进度条 */
|
||||
#nprogress {
|
||||
position: fixed;
|
||||
z-index: $base-z-index + 3;
|
||||
|
||||
.bar {
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.peg {
|
||||
box-shadow:
|
||||
0 0 10px var(--el-color-primary),
|
||||
0 0 5px var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
/* el-table表格 */
|
||||
.el-table {
|
||||
.el-table__body-wrapper {
|
||||
@include base-scrollbar;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f7fa !important;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
.cell {
|
||||
font-size: $base-font-size-default;
|
||||
font-weight: normal;
|
||||
color: #606266;
|
||||
|
||||
.el-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: $base-border-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* el-pagination分页 */
|
||||
.el-pagination {
|
||||
justify-content: center;
|
||||
margin: $base-margin 0 0 0;
|
||||
font-weight: normal;
|
||||
color: var(--el-color-black);
|
||||
}
|
||||
|
||||
/* el-menu菜单开始 */
|
||||
.el-menu,
|
||||
.vab-column-grid {
|
||||
user-select: none;
|
||||
|
||||
/* plus处理图标间距 */
|
||||
div,
|
||||
li,
|
||||
span {
|
||||
i+span {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&.vab-column-grid-card,
|
||||
&.vab-column-grid-vertical {
|
||||
div {
|
||||
i+span {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
/* el-dialog、el-message-box、el-popover、el-button、el-tag */
|
||||
@media (max-width: 576px) {
|
||||
|
||||
.el-dialog,
|
||||
.el-message-box,
|
||||
.el-popover.el-popper {
|
||||
width: 95% !important;
|
||||
}
|
||||
|
||||
|
||||
.el-button {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* el-card卡片 */
|
||||
.el-card {
|
||||
margin-bottom: $base-margin;
|
||||
border-radius: var(--el-border-radius-base);
|
||||
|
||||
&__header {
|
||||
position: relative;
|
||||
|
||||
.card-header-tag {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: $base-padding;
|
||||
}
|
||||
}
|
||||
|
||||
/* .vab-hey-message */
|
||||
.vab-hey-message {
|
||||
@mixin vab-hey-message {
|
||||
padding: 15px;
|
||||
background-color: var(--el-color-white);
|
||||
border-color: var(--el-color-white);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.15);
|
||||
|
||||
.el-message__content {
|
||||
padding-right: $base-padding;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
.el-icon-close {
|
||||
color: #34495e;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
@include vab-hey-message;
|
||||
|
||||
i {
|
||||
color: $base-color-grey;
|
||||
}
|
||||
}
|
||||
|
||||
&-success {
|
||||
@include vab-hey-message;
|
||||
|
||||
i {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
&-warning {
|
||||
@include vab-hey-message;
|
||||
|
||||
i {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
@include vab-hey-message;
|
||||
|
||||
i {
|
||||
color: var(--el-color-error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vab-table-expand */
|
||||
.vab-table-expand {
|
||||
padding: $base-padding;
|
||||
line-height: 30px;
|
||||
|
||||
&-title {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
:not(.no-background-container).auto-height-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: var(--el-container-height);
|
||||
|
||||
.el-table {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
//margin-right: -20px;
|
||||
|
||||
.vab-auto-box {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 0 var(--el-padding) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-card__body {
|
||||
overflow: hidden;
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
library/styles/variables/vab-blue-variables.module.scss
Normal file
43
library/styles/variables/vab-blue-variables.module.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
$base-color-primary: #1890ff;
|
||||
$base-color-success: #13ce66;
|
||||
$base-color-warning: #ffba00;
|
||||
$base-color-danger: #ff4d4f;
|
||||
$base-color-error: #ff4d4f;
|
||||
$base-color-transition: #77e19d;
|
||||
|
||||
:export {
|
||||
vab-color-grey: $base-color-grey;
|
||||
vab-color-black: $base-color-black;
|
||||
vab-color-primary: $base-color-primary;
|
||||
vab-color-primary-light-1: mix($base-color-white, $base-color-primary, 10%);
|
||||
vab-color-primary-light-2: mix($base-color-white, $base-color-primary, 20%);
|
||||
vab-color-primary-light-3: mix($base-color-white, $base-color-primary, 30%);
|
||||
vab-color-primary-light-4: mix($base-color-white, $base-color-primary, 40%);
|
||||
vab-color-primary-light-5: mix($base-color-white, $base-color-primary, 50%);
|
||||
vab-color-primary-light-6: mix($base-color-white, $base-color-primary, 60%);
|
||||
vab-color-primary-light-7: mix($base-color-white, $base-color-primary, 70%);
|
||||
vab-color-primary-light-8: mix($base-color-white, $base-color-primary, 80%);
|
||||
vab-color-primary-light-9: mix($base-color-white, $base-color-primary, 90%);
|
||||
vab-color-success: $base-color-success;
|
||||
vab-color-success-light: mix($base-color-white, $base-color-success, 80%);
|
||||
vab-color-success-lighter: mix($base-color-white, $base-color-success, 90%);
|
||||
vab-color-warning: $base-color-warning;
|
||||
vab-color-warning-light: mix($base-color-white, $base-color-warning, 80%);
|
||||
vab-color-warning-lighter: mix($base-color-white, $base-color-warning, 90%);
|
||||
vab-color-danger: $base-color-danger;
|
||||
vab-color-danger-light: mix($base-color-white, $base-color-danger, 80%);
|
||||
vab-color-danger-lighter: mix($base-color-white, $base-color-danger, 90%);
|
||||
vab-color-error: $base-color-error;
|
||||
vab-color-error-light: mix($base-color-white, $base-color-error, 80%);
|
||||
vab-color-error-lighter: mix($base-color-white, $base-color-error, 90%);
|
||||
vab-color-info: $base-color-text-secondary;
|
||||
vab-color-info-light: mix($base-color-white,
|
||||
$base-color-text-secondary,
|
||||
80%);
|
||||
vab-color-info-lighter: mix($base-color-white,
|
||||
$base-color-text-secondary,
|
||||
90%);
|
||||
vab-border-radius-base: 5px;
|
||||
vab-color-transition: $base-color-transition;
|
||||
vab-left-menu-width: $base-left-menu-width;
|
||||
}
|
||||
43
library/styles/variables/vab-green-variables.module.scss
Normal file
43
library/styles/variables/vab-green-variables.module.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
$base-color-primary: #41b584;
|
||||
$base-color-success: #13ce66;
|
||||
$base-color-warning: #ffba00;
|
||||
$base-color-danger: #ff4d4f;
|
||||
$base-color-error: #ff4d4f;
|
||||
$base-color-transition: #1890ff;
|
||||
|
||||
:export {
|
||||
vab-color-grey: $base-color-grey;
|
||||
vab-color-black: $base-color-black;
|
||||
vab-color-primary: $base-color-primary;
|
||||
vab-color-primary-light-1: mix($base-color-white, $base-color-primary, 10%);
|
||||
vab-color-primary-light-2: mix($base-color-white, $base-color-primary, 20%);
|
||||
vab-color-primary-light-3: mix($base-color-white, $base-color-primary, 30%);
|
||||
vab-color-primary-light-4: mix($base-color-white, $base-color-primary, 40%);
|
||||
vab-color-primary-light-5: mix($base-color-white, $base-color-primary, 50%);
|
||||
vab-color-primary-light-6: mix($base-color-white, $base-color-primary, 60%);
|
||||
vab-color-primary-light-7: mix($base-color-white, $base-color-primary, 70%);
|
||||
vab-color-primary-light-8: mix($base-color-white, $base-color-primary, 80%);
|
||||
vab-color-primary-light-9: mix($base-color-white, $base-color-primary, 90%);
|
||||
vab-color-success: $base-color-success;
|
||||
vab-color-success-light: mix($base-color-white, $base-color-success, 80%);
|
||||
vab-color-success-lighter: mix($base-color-white, $base-color-success, 90%);
|
||||
vab-color-warning: $base-color-warning;
|
||||
vab-color-warning-light: mix($base-color-white, $base-color-warning, 80%);
|
||||
vab-color-warning-lighter: mix($base-color-white, $base-color-warning, 90%);
|
||||
vab-color-danger: $base-color-danger;
|
||||
vab-color-danger-light: mix($base-color-white, $base-color-danger, 80%);
|
||||
vab-color-danger-lighter: mix($base-color-white, $base-color-danger, 90%);
|
||||
vab-color-error: $base-color-error;
|
||||
vab-color-error-light: mix($base-color-white, $base-color-error, 80%);
|
||||
vab-color-error-lighter: mix($base-color-white, $base-color-error, 90%);
|
||||
vab-color-info: $base-color-text-secondary;
|
||||
vab-color-info-light: mix($base-color-white,
|
||||
$base-color-text-secondary,
|
||||
80%);
|
||||
vab-color-info-lighter: mix($base-color-white,
|
||||
$base-color-text-secondary,
|
||||
90%);
|
||||
vab-border-radius-base: 5px;
|
||||
vab-color-transition: $base-color-transition;
|
||||
vab-left-menu-width: $base-left-menu-width;
|
||||
}
|
||||
43
library/styles/variables/vab-purple-variables.module.scss
Normal file
43
library/styles/variables/vab-purple-variables.module.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
$base-color-primary: #6954f0;
|
||||
$base-color-success: #13ce66;
|
||||
$base-color-warning: #ffba00;
|
||||
$base-color-danger: #ff4d4f;
|
||||
$base-color-error: #ff4d4f;
|
||||
$base-color-transition: #1890ff;
|
||||
|
||||
:export {
|
||||
vab-color-grey: $base-color-grey;
|
||||
vab-color-black: $base-color-black;
|
||||
vab-color-primary: $base-color-primary;
|
||||
vab-color-primary-light-1: mix($base-color-white, $base-color-primary, 10%);
|
||||
vab-color-primary-light-2: mix($base-color-white, $base-color-primary, 20%);
|
||||
vab-color-primary-light-3: mix($base-color-white, $base-color-primary, 30%);
|
||||
vab-color-primary-light-4: mix($base-color-white, $base-color-primary, 40%);
|
||||
vab-color-primary-light-5: mix($base-color-white, $base-color-primary, 50%);
|
||||
vab-color-primary-light-6: mix($base-color-white, $base-color-primary, 60%);
|
||||
vab-color-primary-light-7: mix($base-color-white, $base-color-primary, 70%);
|
||||
vab-color-primary-light-8: mix($base-color-white, $base-color-primary, 80%);
|
||||
vab-color-primary-light-9: mix($base-color-white, $base-color-primary, 90%);
|
||||
vab-color-success: $base-color-success;
|
||||
vab-color-success-light: mix($base-color-white, $base-color-success, 80%);
|
||||
vab-color-success-lighter: mix($base-color-white, $base-color-success, 90%);
|
||||
vab-color-warning: $base-color-warning;
|
||||
vab-color-warning-light: mix($base-color-white, $base-color-warning, 80%);
|
||||
vab-color-warning-lighter: mix($base-color-white, $base-color-warning, 90%);
|
||||
vab-color-danger: $base-color-danger;
|
||||
vab-color-danger-light: mix($base-color-white, $base-color-danger, 80%);
|
||||
vab-color-danger-lighter: mix($base-color-white, $base-color-danger, 90%);
|
||||
vab-color-error: $base-color-error;
|
||||
vab-color-error-light: mix($base-color-white, $base-color-error, 80%);
|
||||
vab-color-error-lighter: mix($base-color-white, $base-color-error, 90%);
|
||||
vab-color-info: $base-color-text-secondary;
|
||||
vab-color-info-light: mix($base-color-white,
|
||||
$base-color-text-secondary,
|
||||
80%);
|
||||
vab-color-info-lighter: mix($base-color-white,
|
||||
$base-color-text-secondary,
|
||||
90%);
|
||||
vab-border-radius-base: 5px;
|
||||
vab-color-transition: $base-color-transition;
|
||||
vab-left-menu-width: $base-left-menu-width;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user