chore: pnpm eslint . --fix
This commit is contained in:
parent
c8571a5f55
commit
7f02e18d37
@ -1,3 +1,41 @@
|
|||||||
import antfu from '@antfu/eslint-config'
|
import antfu from "@antfu/eslint-config"
|
||||||
|
|
||||||
export default antfu()
|
// 更多自定义配置可查阅仓库:https://github.com/antfu/eslint-config
|
||||||
|
export default antfu(
|
||||||
|
{
|
||||||
|
// 使用外部格式化程序格式化 css、html、markdown 等文件
|
||||||
|
formatters: true,
|
||||||
|
// 启用样式规则
|
||||||
|
stylistic: {
|
||||||
|
// 缩进级别
|
||||||
|
indent: 2,
|
||||||
|
// 引号风格 'single' | 'double'
|
||||||
|
quotes: "double",
|
||||||
|
// 是否启用分号
|
||||||
|
semi: false
|
||||||
|
},
|
||||||
|
// 忽略文件
|
||||||
|
ignores: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 对所有文件都生效的规则
|
||||||
|
rules: {
|
||||||
|
// vue
|
||||||
|
"vue/block-order": ["error", { order: ["script", "template", "style"] }],
|
||||||
|
// ts
|
||||||
|
"ts/no-use-before-define": "off",
|
||||||
|
// node
|
||||||
|
"node/prefer-global/process": "off",
|
||||||
|
// style
|
||||||
|
"style/comma-dangle": ["error", "never"],
|
||||||
|
"style/brace-style": "off",
|
||||||
|
// regexp
|
||||||
|
"regexp/no-unused-capturing-group": "off",
|
||||||
|
// other
|
||||||
|
"no-console": "off",
|
||||||
|
"no-debugger": "off",
|
||||||
|
"symbol-description": "off",
|
||||||
|
"antfu/if-newline": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
"@vitejs/plugin-vue-jsx": "4.1.0",
|
"@vitejs/plugin-vue-jsx": "4.1.0",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.4.6",
|
||||||
"eslint": "9.15.0",
|
"eslint": "9.15.0",
|
||||||
|
"eslint-plugin-format": "0.1.2",
|
||||||
"husky": "9.1.6",
|
"husky": "9.1.6",
|
||||||
"jsdom": "25.0.1",
|
"jsdom": "25.0.1",
|
||||||
"lint-staged": "15.2.10",
|
"lint-staged": "15.2.10",
|
||||||
|
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@ -65,7 +65,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@antfu/eslint-config':
|
'@antfu/eslint-config':
|
||||||
specifier: 3.9.1
|
specifier: 3.9.1
|
||||||
version: 3.9.1(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1)(sass@1.78.0))
|
version: 3.9.1(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@0.1.2(eslint@9.15.0(jiti@1.21.6)))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1)(sass@1.78.0))
|
||||||
'@types/js-cookie':
|
'@types/js-cookie':
|
||||||
specifier: 3.0.6
|
specifier: 3.0.6
|
||||||
version: 3.0.6
|
version: 3.0.6
|
||||||
@ -93,6 +93,9 @@ importers:
|
|||||||
eslint:
|
eslint:
|
||||||
specifier: 9.15.0
|
specifier: 9.15.0
|
||||||
version: 9.15.0(jiti@1.21.6)
|
version: 9.15.0(jiti@1.21.6)
|
||||||
|
eslint-plugin-format:
|
||||||
|
specifier: 0.1.2
|
||||||
|
version: 0.1.2(eslint@9.15.0(jiti@1.21.6))
|
||||||
husky:
|
husky:
|
||||||
specifier: 9.1.6
|
specifier: 9.1.6
|
||||||
version: 9.1.6
|
version: 9.1.6
|
||||||
@ -313,6 +316,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@dprint/formatter@0.3.0':
|
||||||
|
resolution: {integrity: sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==}
|
||||||
|
|
||||||
|
'@dprint/markdown@0.17.8':
|
||||||
|
resolution: {integrity: sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==}
|
||||||
|
|
||||||
|
'@dprint/toml@0.6.3':
|
||||||
|
resolution: {integrity: sha512-zQ42I53sb4WVHA+5yoY1t59Zk++Ot02AvUgtNKLzTT8mPyVqVChFcePa3on/xIoKEgH+RoepgPHzqfk9837YFw==}
|
||||||
|
|
||||||
'@element-plus/icons-vue@2.3.1':
|
'@element-plus/icons-vue@2.3.1':
|
||||||
resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
|
resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1808,6 +1820,11 @@ packages:
|
|||||||
eslint-flat-config-utils@0.4.0:
|
eslint-flat-config-utils@0.4.0:
|
||||||
resolution: {integrity: sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==}
|
resolution: {integrity: sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==}
|
||||||
|
|
||||||
|
eslint-formatting-reporter@0.0.0:
|
||||||
|
resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: '>=8.40.0'
|
||||||
|
|
||||||
eslint-import-resolver-node@0.3.9:
|
eslint-import-resolver-node@0.3.9:
|
||||||
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
|
||||||
|
|
||||||
@ -1827,6 +1844,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '*'
|
eslint: '*'
|
||||||
|
|
||||||
|
eslint-parser-plain@0.1.0:
|
||||||
|
resolution: {integrity: sha512-oOeA6FWU0UJT/Rxc3XF5Cq0nbIZbylm7j8+plqq0CZoE6m4u32OXJrR+9iy4srGMmF6v6pmgvP1zPxSRIGh3sg==}
|
||||||
|
|
||||||
eslint-plugin-antfu@2.7.0:
|
eslint-plugin-antfu@2.7.0:
|
||||||
resolution: {integrity: sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==}
|
resolution: {integrity: sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1843,6 +1863,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=8'
|
eslint: '>=8'
|
||||||
|
|
||||||
|
eslint-plugin-format@0.1.2:
|
||||||
|
resolution: {integrity: sha512-ZrcO3aiumgJ6ENAv65IWkPjtW77ML/5mp0YrRK0jdvvaZJb+4kKWbaQTMr/XbJo6CtELRmCApAziEKh7L2NbdQ==}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.40.0 || ^9.0.0
|
||||||
|
|
||||||
eslint-plugin-import-x@4.4.2:
|
eslint-plugin-import-x@4.4.2:
|
||||||
resolution: {integrity: sha512-mDRXPSLQ0UQZQw91QdG4/qZT6hgeW2MJTczAbgPseUZuPEtIjjdPOolXroRkulnOn3fzj6gNgvk+wchMJiHElg==}
|
resolution: {integrity: sha512-mDRXPSLQ0UQZQw91QdG4/qZT6hgeW2MJTczAbgPseUZuPEtIjjdPOolXroRkulnOn3fzj6gNgvk+wchMJiHElg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@ -2025,6 +2050,9 @@ packages:
|
|||||||
fast-deep-equal@3.1.3:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
|
fast-diff@1.3.0:
|
||||||
|
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
||||||
|
|
||||||
fast-glob@3.3.2:
|
fast-glob@3.3.2:
|
||||||
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
|
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
|
||||||
engines: {node: '>=8.6.0'}
|
engines: {node: '>=8.6.0'}
|
||||||
@ -3150,6 +3178,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
prettier-linter-helpers@1.0.0:
|
||||||
|
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
prettier@3.3.3:
|
prettier@3.3.3:
|
||||||
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
|
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@ -3974,7 +4006,7 @@ snapshots:
|
|||||||
'@jridgewell/gen-mapping': 0.3.5
|
'@jridgewell/gen-mapping': 0.3.5
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
'@antfu/eslint-config@3.9.1(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1)(sass@1.78.0))':
|
'@antfu/eslint-config@3.9.1(@typescript-eslint/utils@8.14.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@0.1.2(eslint@9.15.0(jiti@1.21.6)))(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)(vitest@2.1.5(@types/node@22.9.0)(jsdom@25.0.1)(sass@1.78.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/install-pkg': 0.4.1
|
'@antfu/install-pkg': 0.4.1
|
||||||
'@clack/prompts': 0.7.0
|
'@clack/prompts': 0.7.0
|
||||||
@ -4012,6 +4044,8 @@ snapshots:
|
|||||||
vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6))
|
vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6))
|
||||||
yaml-eslint-parser: 1.2.3
|
yaml-eslint-parser: 1.2.3
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
|
optionalDependencies:
|
||||||
|
eslint-plugin-format: 0.1.2(eslint@9.15.0(jiti@1.21.6))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@eslint/json'
|
- '@eslint/json'
|
||||||
- '@typescript-eslint/utils'
|
- '@typescript-eslint/utils'
|
||||||
@ -4206,6 +4240,12 @@ snapshots:
|
|||||||
|
|
||||||
'@ctrl/tinycolor@3.6.1': {}
|
'@ctrl/tinycolor@3.6.1': {}
|
||||||
|
|
||||||
|
'@dprint/formatter@0.3.0': {}
|
||||||
|
|
||||||
|
'@dprint/markdown@0.17.8': {}
|
||||||
|
|
||||||
|
'@dprint/toml@0.6.3': {}
|
||||||
|
|
||||||
'@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.6.3))':
|
'@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.6.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.13(typescript@5.6.3)
|
vue: 3.5.13(typescript@5.6.3)
|
||||||
@ -5784,6 +5824,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
|
|
||||||
|
eslint-formatting-reporter@0.0.0(eslint@9.15.0(jiti@1.21.6)):
|
||||||
|
dependencies:
|
||||||
|
eslint: 9.15.0(jiti@1.21.6)
|
||||||
|
prettier-linter-helpers: 1.0.0
|
||||||
|
|
||||||
eslint-import-resolver-node@0.3.9:
|
eslint-import-resolver-node@0.3.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
@ -5802,6 +5847,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.15.0(jiti@1.21.6)
|
eslint: 9.15.0(jiti@1.21.6)
|
||||||
|
|
||||||
|
eslint-parser-plain@0.1.0: {}
|
||||||
|
|
||||||
eslint-plugin-antfu@2.7.0(eslint@9.15.0(jiti@1.21.6)):
|
eslint-plugin-antfu@2.7.0(eslint@9.15.0(jiti@1.21.6)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/utils': 0.7.10
|
'@antfu/utils': 0.7.10
|
||||||
@ -5819,6 +5866,17 @@ snapshots:
|
|||||||
eslint: 9.15.0(jiti@1.21.6)
|
eslint: 9.15.0(jiti@1.21.6)
|
||||||
eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6))
|
eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6))
|
||||||
|
|
||||||
|
eslint-plugin-format@0.1.2(eslint@9.15.0(jiti@1.21.6)):
|
||||||
|
dependencies:
|
||||||
|
'@dprint/formatter': 0.3.0
|
||||||
|
'@dprint/markdown': 0.17.8
|
||||||
|
'@dprint/toml': 0.6.3
|
||||||
|
eslint: 9.15.0(jiti@1.21.6)
|
||||||
|
eslint-formatting-reporter: 0.0.0(eslint@9.15.0(jiti@1.21.6))
|
||||||
|
eslint-parser-plain: 0.1.0
|
||||||
|
prettier: 3.3.3
|
||||||
|
synckit: 0.9.2
|
||||||
|
|
||||||
eslint-plugin-import-x@4.4.2(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
|
eslint-plugin-import-x@4.4.2(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/utils': 8.14.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
|
'@typescript-eslint/utils': 8.14.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
|
||||||
@ -6110,6 +6168,8 @@ snapshots:
|
|||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
|
fast-diff@1.3.0: {}
|
||||||
|
|
||||||
fast-glob@3.3.2:
|
fast-glob@3.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
@ -7379,6 +7439,10 @@ snapshots:
|
|||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
|
prettier-linter-helpers@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
fast-diff: 1.3.0
|
||||||
|
|
||||||
prettier@3.3.3: {}
|
prettier@3.3.3: {}
|
||||||
|
|
||||||
proto-list@1.2.4: {}
|
proto-list@1.2.4: {}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
// Tip: Simple judgments may not fully cover
|
// Tip: Simple judgments may not fully cover
|
||||||
if (/MSIE\s|Trident\//.test(window.navigator.userAgent)) {
|
if (/MSIE\s|Trident\//.test(window.navigator.userAgent)) {
|
||||||
document.body.innerHTML =
|
document.body.innerHTML = "<strong>Sorry, this browser is currently not supported. We recommend using the latest version of a modern browser. For example, Chrome/Firefox/Edge.</strong>"
|
||||||
"<strong>Sorry, this browser is currently not supported. We recommend using the latest version of a modern browser. For example, Chrome/Firefox/Edge.</strong>"
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTheme } from "@/hooks/useTheme"
|
|
||||||
import { useGreyAndColorWeakness } from "@/hooks/useGreyAndColorWeakness"
|
import { useGreyAndColorWeakness } from "@/hooks/useGreyAndColorWeakness"
|
||||||
|
import { useTheme } from "@/hooks/useTheme"
|
||||||
import { ElNotification } from "element-plus"
|
import { ElNotification } from "element-plus"
|
||||||
import zhCn from "element-plus/es/locale/lang/zh-cn" // Element Plus 中文包
|
import zhCn from "element-plus/es/locale/lang/zh-cn" // Element Plus 中文包
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { request } from "@/utils/service"
|
|
||||||
import type * as Login from "./types/login"
|
import type * as Login from "./types/login"
|
||||||
|
import { request } from "@/utils/service"
|
||||||
|
|
||||||
/** 获取登录验证码 */
|
/** 获取登录验证码 */
|
||||||
export function getLoginCodeApi() {
|
export function getLoginCodeApi() {
|
||||||
|
@ -11,4 +11,4 @@ export type LoginCodeResponseData = ApiResponseData<string>
|
|||||||
|
|
||||||
export type LoginResponseData = ApiResponseData<{ token: string }>
|
export type LoginResponseData = ApiResponseData<{ token: string }>
|
||||||
|
|
||||||
export type UserInfoResponseData = ApiResponseData<{ username: string; roles: string[] }>
|
export type UserInfoResponseData = ApiResponseData<{ username: string, roles: string[] }>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { request } from "@/utils/service"
|
|
||||||
import type * as Table from "./types/table"
|
import type * as Table from "./types/table"
|
||||||
|
import { request } from "@/utils/service"
|
||||||
|
|
||||||
/** 增 */
|
/** 增 */
|
||||||
export function createTableDataApi(data: Table.CreateOrUpdateTableRequestData) {
|
export function createTableDataApi(data: Table.CreateOrUpdateTableRequestData) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type ListItem } from "./data"
|
import type { ListItem } from "./data"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
list: ListItem[]
|
list: ListItem[]
|
||||||
@ -10,7 +10,7 @@ const props = defineProps<Props>()
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-empty v-if="props.list.length === 0" />
|
<el-empty v-if="props.list.length === 0" />
|
||||||
<el-card v-else v-for="(item, index) in props.list" :key="index" shadow="never" class="card-container">
|
<el-card v-for="(item, index) in props.list" v-else :key="index" shadow="never" class="card-container">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div>
|
<div>
|
||||||
@ -18,10 +18,12 @@ const props = defineProps<Props>()
|
|||||||
<span class="card-title">{{ item.title }}</span>
|
<span class="card-title">{{ item.title }}</span>
|
||||||
<el-tag v-if="item.extra" :type="item.status" effect="plain" size="small">{{ item.extra }}</el-tag>
|
<el-tag v-if="item.extra" :type="item.status" effect="plain" size="small">{{ item.extra }}</el-tag>
|
||||||
</span>
|
</span>
|
||||||
<div class="card-time">{{ item.datetime }}</div>
|
<div class="card-time">
|
||||||
|
{{ item.datetime }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.avatar" class="card-avatar">
|
<div v-if="item.avatar" class="card-avatar">
|
||||||
<img :src="item.avatar" width="34" />
|
<img :src="item.avatar" width="34">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from "vue"
|
import type { ListItem } from "./data"
|
||||||
import { ElMessage } from "element-plus"
|
|
||||||
import { Bell } from "@element-plus/icons-vue"
|
import { Bell } from "@element-plus/icons-vue"
|
||||||
|
import { ElMessage } from "element-plus"
|
||||||
|
import { computed, ref } from "vue"
|
||||||
|
import { messageData, notifyData, todoData } from "./data"
|
||||||
import NotifyList from "./NotifyList.vue"
|
import NotifyList from "./NotifyList.vue"
|
||||||
import { type ListItem, notifyData, messageData, todoData } from "./data"
|
|
||||||
|
|
||||||
type TabName = "通知" | "消息" | "待办"
|
type TabName = "通知" | "消息" | "待办"
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ const data = ref<DataItem[]>([
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const handleHistory = () => {
|
function handleHistory() {
|
||||||
ElMessage.success(`跳转到${activeName.value}历史页面`)
|
ElMessage.success(`跳转到${activeName.value}历史页面`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -64,7 +65,7 @@ const handleHistory = () => {
|
|||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-tabs v-model="activeName" class="demo-tabs" stretch>
|
<el-tabs v-model="activeName" class="demo-tabs" stretch>
|
||||||
<el-tab-pane v-for="(item, index) in data" :name="item.name" :key="index">
|
<el-tab-pane v-for="(item, index) in data" :key="index" :name="item.name">
|
||||||
<template #label>
|
<template #label>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
<el-badge :value="item.list.length" :max="badgeMax" :type="item.type" />
|
<el-badge :value="item.list.length" :max="badgeMax" :type="item.type" />
|
||||||
@ -75,7 +76,9 @@ const handleHistory = () => {
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<div class="notify-history">
|
<div class="notify-history">
|
||||||
<el-button link @click="handleHistory">查看{{ activeName }}历史</el-button>
|
<el-button link @click="handleHistory">
|
||||||
|
查看{{ activeName }}历史
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, watchEffect } from "vue"
|
|
||||||
import { ElMessage } from "element-plus"
|
import { ElMessage } from "element-plus"
|
||||||
import screenfull from "screenfull"
|
import screenfull from "screenfull"
|
||||||
|
import { computed, ref, watchEffect } from "vue"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** 全屏的元素,默认是 html */
|
/** 全屏的元素,默认是 html */
|
||||||
@ -31,11 +31,11 @@ const isFullscreen = ref<boolean>(false)
|
|||||||
const fullscreenTips = computed(() => (isFullscreen.value ? props.exitTips : props.openTips))
|
const fullscreenTips = computed(() => (isFullscreen.value ? props.exitTips : props.openTips))
|
||||||
const fullscreenSvgName = computed(() => (isFullscreen.value ? "fullscreen-exit" : "fullscreen"))
|
const fullscreenSvgName = computed(() => (isFullscreen.value ? "fullscreen-exit" : "fullscreen"))
|
||||||
|
|
||||||
const handleFullscreenClick = () => {
|
function handleFullscreenClick() {
|
||||||
const dom = document.querySelector(props.element) || undefined
|
const dom = document.querySelector(props.element) || undefined
|
||||||
isEnabled ? screenfull.toggle(dom) : ElMessage.warning("您的浏览器无法工作")
|
isEnabled ? screenfull.toggle(dom) : ElMessage.warning("您的浏览器无法工作")
|
||||||
}
|
}
|
||||||
const handleFullscreenChange = () => {
|
function handleFullscreenChange() {
|
||||||
isFullscreen.value = screenfull.isFullscreen
|
isFullscreen.value = screenfull.isFullscreen
|
||||||
// 退出全屏时清除相关的 class
|
// 退出全屏时清除相关的 class
|
||||||
isFullscreen.value || classList.remove(CONTENT_LARGE, CONTENT_FULL)
|
isFullscreen.value || classList.remove(CONTENT_LARGE, CONTENT_FULL)
|
||||||
@ -54,12 +54,12 @@ watchEffect((onCleanup) => {
|
|||||||
const isContentLarge = ref<boolean>(false)
|
const isContentLarge = ref<boolean>(false)
|
||||||
const contentLargeTips = computed(() => (isContentLarge.value ? "内容区复原" : "内容区放大"))
|
const contentLargeTips = computed(() => (isContentLarge.value ? "内容区复原" : "内容区放大"))
|
||||||
const contentLargeSvgName = computed(() => (isContentLarge.value ? "fullscreen-exit" : "fullscreen"))
|
const contentLargeSvgName = computed(() => (isContentLarge.value ? "fullscreen-exit" : "fullscreen"))
|
||||||
const handleContentLargeClick = () => {
|
function handleContentLargeClick() {
|
||||||
isContentLarge.value = !isContentLarge.value
|
isContentLarge.value = !isContentLarge.value
|
||||||
// 内容区放大时,将不需要的组件隐藏
|
// 内容区放大时,将不需要的组件隐藏
|
||||||
classList.toggle(CONTENT_LARGE, isContentLarge.value)
|
classList.toggle(CONTENT_LARGE, isContentLarge.value)
|
||||||
}
|
}
|
||||||
const handleContentFullClick = () => {
|
function handleContentFullClick() {
|
||||||
// 取消内容区放大
|
// 取消内容区放大
|
||||||
isContentLarge.value && handleContentLargeClick()
|
isContentLarge.value && handleContentLargeClick()
|
||||||
// 内容区全屏时,将不需要的组件隐藏
|
// 内容区全屏时,将不需要的组件隐藏
|
||||||
@ -82,9 +82,13 @@ const handleContentFullClick = () => {
|
|||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<!-- 内容区放大 -->
|
<!-- 内容区放大 -->
|
||||||
<el-dropdown-item @click="handleContentLargeClick">{{ contentLargeTips }}</el-dropdown-item>
|
<el-dropdown-item @click="handleContentLargeClick">
|
||||||
|
{{ contentLargeTips }}
|
||||||
|
</el-dropdown-item>
|
||||||
<!-- 内容区全屏 -->
|
<!-- 内容区全屏 -->
|
||||||
<el-dropdown-item @click="handleContentFullClick">内容区全屏</el-dropdown-item>
|
<el-dropdown-item @click="handleContentFullClick">
|
||||||
|
内容区全屏
|
||||||
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, shallowRef } from "vue"
|
import type { RouteRecordName, RouteRecordRaw } from "vue-router"
|
||||||
import { type RouteRecordName, type RouteRecordRaw, useRouter } from "vue-router"
|
import { useDevice } from "@/hooks/useDevice"
|
||||||
import { usePermissionStore } from "@/store/modules/permission"
|
import { usePermissionStore } from "@/store/modules/permission"
|
||||||
import SearchResult from "./SearchResult.vue"
|
import { isExternal } from "@/utils/validate"
|
||||||
import SearchFooter from "./SearchFooter.vue"
|
|
||||||
import { ElMessage, ElScrollbar } from "element-plus"
|
import { ElMessage, ElScrollbar } from "element-plus"
|
||||||
import { cloneDeep, debounce } from "lodash-es"
|
import { cloneDeep, debounce } from "lodash-es"
|
||||||
import { useDevice } from "@/hooks/useDevice"
|
import { computed, ref, shallowRef } from "vue"
|
||||||
import { isExternal } from "@/utils/validate"
|
import { useRouter } from "vue-router"
|
||||||
|
import SearchFooter from "./SearchFooter.vue"
|
||||||
|
import SearchResult from "./SearchResult.vue"
|
||||||
|
|
||||||
/** 控制 modal 显隐 */
|
/** 控制 modal 显隐 */
|
||||||
const modelValue = defineModel<boolean>({ required: true })
|
const modelValue = defineModel<boolean>({ required: true })
|
||||||
@ -33,7 +34,7 @@ const menusData = computed(() => cloneDeep(usePermissionStore().routes))
|
|||||||
/** 搜索(防抖) */
|
/** 搜索(防抖) */
|
||||||
const handleSearch = debounce(() => {
|
const handleSearch = debounce(() => {
|
||||||
const flatMenusData = flatTree(menusData.value)
|
const flatMenusData = flatTree(menusData.value)
|
||||||
resultList.value = flatMenusData.filter((menu) =>
|
resultList.value = flatMenusData.filter(menu =>
|
||||||
keyword.value ? menu.meta?.title?.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim()) : false
|
keyword.value ? menu.meta?.title?.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim()) : false
|
||||||
)
|
)
|
||||||
// 默认选中搜索结果的第一项
|
// 默认选中搜索结果的第一项
|
||||||
@ -42,7 +43,7 @@ const handleSearch = debounce(() => {
|
|||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
/** 将树形菜单扁平化为一维数组,用于菜单搜索 */
|
/** 将树形菜单扁平化为一维数组,用于菜单搜索 */
|
||||||
const flatTree = (arr: RouteRecordRaw[], result: RouteRecordRaw[] = []) => {
|
function flatTree(arr: RouteRecordRaw[], result: RouteRecordRaw[] = []) {
|
||||||
arr.forEach((item) => {
|
arr.forEach((item) => {
|
||||||
result.push(item)
|
result.push(item)
|
||||||
item.children && flatTree(item.children, result)
|
item.children && flatTree(item.children, result)
|
||||||
@ -51,7 +52,7 @@ const flatTree = (arr: RouteRecordRaw[], result: RouteRecordRaw[] = []) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭搜索对话框 */
|
/** 关闭搜索对话框 */
|
||||||
const handleClose = () => {
|
function handleClose() {
|
||||||
modelValue.value = false
|
modelValue.value = false
|
||||||
// 延时处理防止用户看到重置数据的操作
|
// 延时处理防止用户看到重置数据的操作
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -61,7 +62,7 @@ const handleClose = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 根据下标位置进行滚动 */
|
/** 根据下标位置进行滚动 */
|
||||||
const scrollTo = (index: number) => {
|
function scrollTo(index: number) {
|
||||||
if (!searchResultRef.value) return
|
if (!searchResultRef.value) return
|
||||||
const scrollTop = searchResultRef.value.getScrollTop(index)
|
const scrollTop = searchResultRef.value.getScrollTop(index)
|
||||||
// 手动控制 el-scrollbar 滚动条滚动,设置滚动条到顶部的距离
|
// 手动控制 el-scrollbar 滚动条滚动,设置滚动条到顶部的距离
|
||||||
@ -69,12 +70,12 @@ const scrollTo = (index: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 键盘上键 */
|
/** 键盘上键 */
|
||||||
const handleUp = () => {
|
function handleUp() {
|
||||||
isPressUpOrDown.value = true
|
isPressUpOrDown.value = true
|
||||||
const { length } = resultList.value
|
const { length } = resultList.value
|
||||||
if (length === 0) return
|
if (length === 0) return
|
||||||
// 获取该 name 在菜单中第一次出现的位置
|
// 获取该 name 在菜单中第一次出现的位置
|
||||||
const index = resultList.value.findIndex((item) => item.name === activeRouteName.value)
|
const index = resultList.value.findIndex(item => item.name === activeRouteName.value)
|
||||||
// 如果已处在顶部
|
// 如果已处在顶部
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
const bottomName = resultList.value[length - 1].name
|
const bottomName = resultList.value[length - 1].name
|
||||||
@ -94,12 +95,12 @@ const handleUp = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 键盘下键 */
|
/** 键盘下键 */
|
||||||
const handleDown = () => {
|
function handleDown() {
|
||||||
isPressUpOrDown.value = true
|
isPressUpOrDown.value = true
|
||||||
const { length } = resultList.value
|
const { length } = resultList.value
|
||||||
if (length === 0) return
|
if (length === 0) return
|
||||||
// 获取该 name 在菜单中最后一次出现的位置(可解决遇到连续两个相同 name 导致的下键不能生效的问题)
|
// 获取该 name 在菜单中最后一次出现的位置(可解决遇到连续两个相同 name 导致的下键不能生效的问题)
|
||||||
const index = resultList.value.map((item) => item.name).lastIndexOf(activeRouteName.value)
|
const index = resultList.value.map(item => item.name).lastIndexOf(activeRouteName.value)
|
||||||
// 如果已处在底部
|
// 如果已处在底部
|
||||||
if (index === length - 1) {
|
if (index === length - 1) {
|
||||||
const topName = resultList.value[0].name
|
const topName = resultList.value[0].name
|
||||||
@ -119,11 +120,11 @@ const handleDown = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 键盘回车键 */
|
/** 键盘回车键 */
|
||||||
const handleEnter = () => {
|
function handleEnter() {
|
||||||
const { length } = resultList.value
|
const { length } = resultList.value
|
||||||
if (length === 0) return
|
if (length === 0) return
|
||||||
const name = activeRouteName.value
|
const name = activeRouteName.value
|
||||||
const path = resultList.value.find((item) => item.name === name)?.path
|
const path = resultList.value.find(item => item.name === name)?.path
|
||||||
if (path && isExternal(path)) {
|
if (path && isExternal(path)) {
|
||||||
window.open(path, "_blank", "noopener, noreferrer")
|
window.open(path, "_blank", "noopener, noreferrer")
|
||||||
return
|
return
|
||||||
@ -134,7 +135,8 @@ const handleEnter = () => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
router.push({ name })
|
router.push({ name })
|
||||||
} catch {
|
}
|
||||||
|
catch {
|
||||||
ElMessage.error("该菜单有必填的动态参数,无法通过搜索进入")
|
ElMessage.error("该菜单有必填的动态参数,无法通过搜索进入")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -142,7 +144,7 @@ const handleEnter = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 释放上键或下键 */
|
/** 释放上键或下键 */
|
||||||
const handleReleaseUpOrDown = () => {
|
function handleReleaseUpOrDown() {
|
||||||
isPressUpOrDown.value = false
|
isPressUpOrDown.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -150,19 +152,19 @@ const handleReleaseUpOrDown = () => {
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
|
:before-close="handleClose"
|
||||||
|
:width="modalWidth"
|
||||||
|
top="5vh"
|
||||||
|
class="search-modal__private"
|
||||||
|
append-to-body
|
||||||
@opened="inputRef?.focus()"
|
@opened="inputRef?.focus()"
|
||||||
@closed="inputRef?.blur()"
|
@closed="inputRef?.blur()"
|
||||||
@keydown.up="handleUp"
|
@keydown.up="handleUp"
|
||||||
@keydown.down="handleDown"
|
@keydown.down="handleDown"
|
||||||
@keydown.enter="handleEnter"
|
@keydown.enter="handleEnter"
|
||||||
@keyup.up.down="handleReleaseUpOrDown"
|
@keyup.up.down="handleReleaseUpOrDown"
|
||||||
:before-close="handleClose"
|
|
||||||
:width="modalWidth"
|
|
||||||
top="5vh"
|
|
||||||
class="search-modal__private"
|
|
||||||
append-to-body
|
|
||||||
>
|
>
|
||||||
<el-input ref="inputRef" v-model="keyword" @input="handleSearch" placeholder="搜索菜单" size="large" clearable>
|
<el-input ref="inputRef" v-model="keyword" placeholder="搜索菜单" size="large" clearable @input="handleSearch">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<SvgIcon name="search" />
|
<SvgIcon name="search" />
|
||||||
</template>
|
</template>
|
||||||
@ -170,15 +172,15 @@ const handleReleaseUpOrDown = () => {
|
|||||||
<el-empty v-if="resultList.length === 0" description="暂无搜索结果" :image-size="100" />
|
<el-empty v-if="resultList.length === 0" description="暂无搜索结果" :image-size="100" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<p>搜索结果</p>
|
<p>搜索结果</p>
|
||||||
<el-scrollbar ref="scrollbarRef" max-height="40vh" always>
|
<ElScrollbar ref="scrollbarRef" max-height="40vh" always>
|
||||||
<SearchResult
|
<SearchResult
|
||||||
ref="searchResultRef"
|
ref="searchResultRef"
|
||||||
v-model="activeRouteName"
|
v-model="activeRouteName"
|
||||||
:list="resultList"
|
:list="resultList"
|
||||||
:isPressUpOrDown="isPressUpOrDown"
|
:is-press-up-or-down="isPressUpOrDown"
|
||||||
@click="handleEnter"
|
@click="handleEnter"
|
||||||
/>
|
/>
|
||||||
</el-scrollbar>
|
</ElScrollbar>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<SearchFooter :total="resultList.length" />
|
<SearchFooter :total="resultList.length" />
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { RouteRecordName, RouteRecordRaw } from "vue-router"
|
||||||
import { getCurrentInstance, onBeforeMount, onBeforeUnmount, onMounted, ref } from "vue"
|
import { getCurrentInstance, onBeforeMount, onBeforeUnmount, onMounted, ref } from "vue"
|
||||||
import { type RouteRecordName, type RouteRecordRaw } from "vue-router"
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
list: RouteRecordRaw[]
|
list: RouteRecordRaw[]
|
||||||
isPressUpOrDown: boolean
|
isPressUpOrDown: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
/** 选中的菜单 */
|
/** 选中的菜单 */
|
||||||
const modelValue = defineModel<RouteRecordName | undefined>({ required: true })
|
const modelValue = defineModel<RouteRecordName | undefined>({ required: true })
|
||||||
const props = defineProps<Props>()
|
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
const instance = getCurrentInstance()
|
||||||
const scrollbarHeight = ref<number>(0)
|
const scrollbarHeight = ref<number>(0)
|
||||||
|
|
||||||
/** 菜单的样式 */
|
/** 菜单的样式 */
|
||||||
const itemStyle = (item: RouteRecordRaw) => {
|
function itemStyle(item: RouteRecordRaw) {
|
||||||
const flag = item.name === modelValue.value
|
const flag = item.name === modelValue.value
|
||||||
return {
|
return {
|
||||||
background: flag ? "var(--el-color-primary)" : "",
|
background: flag ? "var(--el-color-primary)" : "",
|
||||||
@ -24,20 +23,20 @@ const itemStyle = (item: RouteRecordRaw) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 鼠标移入 */
|
/** 鼠标移入 */
|
||||||
const handleMouseenter = (item: RouteRecordRaw) => {
|
function handleMouseenter(item: RouteRecordRaw) {
|
||||||
// 如果上键或下键与 mouseenter 事件同时生效,则以上下键为准,不执行该函数的赋值逻辑
|
// 如果上键或下键与 mouseenter 事件同时生效,则以上下键为准,不执行该函数的赋值逻辑
|
||||||
if (props.isPressUpOrDown) return
|
if (props.isPressUpOrDown) return
|
||||||
modelValue.value = item.name
|
modelValue.value = item.name
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 计算滚动可视区高度 */
|
/** 计算滚动可视区高度 */
|
||||||
const getScrollbarHeight = () => {
|
function getScrollbarHeight() {
|
||||||
// el-scrollbar max-height="40vh"
|
// el-scrollbar max-height="40vh"
|
||||||
scrollbarHeight.value = Number((window.innerHeight * 0.4).toFixed(1))
|
scrollbarHeight.value = Number((window.innerHeight * 0.4).toFixed(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 根据下标计算到顶部的距离 */
|
/** 根据下标计算到顶部的距离 */
|
||||||
const getScrollTop = (index: number) => {
|
function getScrollTop(index: number) {
|
||||||
const currentInstance = instance?.proxy?.$refs[`resultItemRef${index}`] as HTMLDivElement[]
|
const currentInstance = instance?.proxy?.$refs[`resultItemRef${index}`] as HTMLDivElement[]
|
||||||
if (!currentInstance) return 0
|
if (!currentInstance) return 0
|
||||||
const currentRef = currentInstance[0]
|
const currentRef = currentInstance[0]
|
||||||
@ -75,7 +74,7 @@ defineExpose({ getScrollTop })
|
|||||||
@mouseenter="handleMouseenter(item)"
|
@mouseenter="handleMouseenter(item)"
|
||||||
>
|
>
|
||||||
<SvgIcon v-if="item.meta?.svgIcon" :name="item.meta.svgIcon" />
|
<SvgIcon v-if="item.meta?.svgIcon" :name="item.meta.svgIcon" />
|
||||||
<component v-else-if="item.meta?.elIcon" :is="item.meta.elIcon" class="el-icon" />
|
<component :is="item.meta.elIcon" v-else-if="item.meta?.elIcon" class="el-icon" />
|
||||||
<span class="result-item-title">
|
<span class="result-item-title">
|
||||||
{{ item.meta?.title }}
|
{{ item.meta?.title }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -5,7 +5,7 @@ import SearchModal from "./SearchModal.vue"
|
|||||||
/** 控制 modal 显隐 */
|
/** 控制 modal 显隐 */
|
||||||
const modalVisible = ref<boolean>(false)
|
const modalVisible = ref<boolean>(false)
|
||||||
/** 打开 modal */
|
/** 打开 modal */
|
||||||
const handleOpen = () => {
|
function handleOpen() {
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type ThemeName, useTheme } from "@/hooks/useTheme"
|
import type { ThemeName } from "@/hooks/useTheme"
|
||||||
|
import { useTheme } from "@/hooks/useTheme"
|
||||||
import { MagicStick } from "@element-plus/icons-vue"
|
import { MagicStick } from "@element-plus/icons-vue"
|
||||||
|
|
||||||
const { themeList, activeThemeName, setTheme } = useTheme()
|
const { themeList, activeThemeName, setTheme } = useTheme()
|
||||||
|
|
||||||
const handleChangeTheme = ({ clientX, clientY }: MouseEvent, themeName: ThemeName) => {
|
function handleChangeTheme({ clientX, clientY }: MouseEvent, themeName: ThemeName) {
|
||||||
const maxRadius = Math.hypot(
|
const maxRadius = Math.hypot(
|
||||||
Math.max(clientX, window.innerWidth - clientX),
|
Math.max(clientX, window.innerWidth - clientX),
|
||||||
Math.max(clientY, window.innerHeight - clientY)
|
Math.max(clientY, window.innerHeight - clientY)
|
||||||
)
|
)
|
||||||
const style = document.documentElement.style
|
const style = document.documentElement.style
|
||||||
style.setProperty("--v3-theme-x", clientX + "px")
|
style.setProperty("--v3-theme-x", `${clientX}px`)
|
||||||
style.setProperty("--v3-theme-y", clientY + "px")
|
style.setProperty("--v3-theme-y", `${clientY}px`)
|
||||||
style.setProperty("--v3-theme-r", maxRadius + "px")
|
style.setProperty("--v3-theme-r", `${maxRadius}px`)
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
setTheme(themeName)
|
setTheme(themeName)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getConfigLayout } from "@/utils/cache/local-storage"
|
|
||||||
import { LayoutModeEnum } from "@/constants/app-key"
|
import { LayoutModeEnum } from "@/constants/app-key"
|
||||||
|
import { getConfigLayout } from "@/utils/cache/local-storage"
|
||||||
|
|
||||||
/** 项目配置类型 */
|
/** 项目配置类型 */
|
||||||
export interface LayoutSettings {
|
export interface LayoutSettings {
|
||||||
|
@ -6,7 +6,8 @@ interface RouteSettings {
|
|||||||
* 2. 假如项目不需要根据不同的用户来显示不同的页面,则应该将 dynamic: false
|
* 2. 假如项目不需要根据不同的用户来显示不同的页面,则应该将 dynamic: false
|
||||||
*/
|
*/
|
||||||
dynamic: boolean
|
dynamic: boolean
|
||||||
/** 当动态路由功能关闭时:
|
/**
|
||||||
|
* 当动态路由功能关闭时:
|
||||||
* 1. 应该将所有路由都写到常驻路由里面(表明所有登录的用户能访问的页面都是一样的)
|
* 1. 应该将所有路由都写到常驻路由里面(表明所有登录的用户能访问的页面都是一样的)
|
||||||
* 2. 系统自动给当前登录用户赋值一个没有任何作用的默认角色
|
* 2. 系统自动给当前登录用户赋值一个没有任何作用的默认角色
|
||||||
*/
|
*/
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type RouteLocationNormalized, type RouteRecordNameGeneric } from "vue-router"
|
import type { RouteLocationNormalized, RouteRecordNameGeneric } from "vue-router"
|
||||||
|
|
||||||
/** 免登录白名单(匹配路由 path) */
|
/** 免登录白名单(匹配路由 path) */
|
||||||
const whiteListByPath: string[] = ["/login"]
|
const whiteListByPath: string[] = ["/login"]
|
||||||
@ -7,9 +7,9 @@ const whiteListByPath: string[] = ["/login"]
|
|||||||
const whiteListByName: RouteRecordNameGeneric[] = []
|
const whiteListByName: RouteRecordNameGeneric[] = []
|
||||||
|
|
||||||
/** 判断是否在白名单 */
|
/** 判断是否在白名单 */
|
||||||
const isWhiteList = (to: RouteLocationNormalized) => {
|
function isWhiteList(to: RouteLocationNormalized) {
|
||||||
// path 和 name 任意一个匹配上即可
|
// path 和 name 任意一个匹配上即可
|
||||||
return whiteListByPath.indexOf(to.path) !== -1 || whiteListByName.indexOf(to.name) !== -1
|
return whiteListByPath.includes(to.path) || whiteListByName.includes(to.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default isWhiteList
|
export default isWhiteList
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type App } from "vue"
|
import type { App } from "vue"
|
||||||
import { permission } from "./permission"
|
import { permission } from "./permission"
|
||||||
|
|
||||||
/** 挂载自定义指令 */
|
/** 挂载自定义指令 */
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type Directive } from "vue"
|
import type { Directive } from "vue"
|
||||||
import { useUserStore } from "@/store/modules/user"
|
import { useUserStore } from "@/store/modules/user"
|
||||||
|
|
||||||
/** 权限指令,和权限判断函数 checkPermission 功能类似 */
|
/** 权限指令,和权限判断函数 checkPermission 功能类似 */
|
||||||
@ -7,7 +7,7 @@ export const permission: Directive = {
|
|||||||
const { value: permissionRoles } = binding
|
const { value: permissionRoles } = binding
|
||||||
const { roles } = useUserStore()
|
const { roles } = useUserStore()
|
||||||
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
|
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
|
||||||
const hasPermission = roles.some((role) => permissionRoles.includes(role))
|
const hasPermission = roles.some(role => permissionRoles.includes(role))
|
||||||
// hasPermission || (el.style.display = "none") // 隐藏
|
// hasPermission || (el.style.display = "none") // 隐藏
|
||||||
hasPermission || el.parentNode?.removeChild(el) // 销毁
|
hasPermission || el.parentNode?.removeChild(el) // 销毁
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { computed } from "vue"
|
|
||||||
import { useAppStore } from "@/store/modules/app"
|
|
||||||
import { DeviceEnum } from "@/constants/app-key"
|
import { DeviceEnum } from "@/constants/app-key"
|
||||||
|
import { useAppStore } from "@/store/modules/app"
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const isMobile = computed(() => appStore.device === DeviceEnum.Mobile)
|
const isMobile = computed(() => appStore.device === DeviceEnum.Mobile)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ref, onMounted } from "vue"
|
import { onMounted, ref } from "vue"
|
||||||
|
|
||||||
type OptionValue = string | number
|
type OptionValue = string | number
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type LoadingOptions, ElLoading } from "element-plus"
|
import { ElLoading, type LoadingOptions } from "element-plus"
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
lock: true,
|
lock: true,
|
||||||
@ -28,7 +28,8 @@ export const useFullscreenLoading: UseFullscreenLoading = (fn, options = {}) =>
|
|||||||
try {
|
try {
|
||||||
loadingInstance = ElLoading.service({ ...defaultOptions, ...options })
|
loadingInstance = ElLoading.service({ ...defaultOptions, ...options })
|
||||||
return await fn(...args)
|
return await fn(...args)
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
loadingInstance?.close()
|
loadingInstance?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { watchEffect } from "vue"
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
|
import { watchEffect } from "vue"
|
||||||
|
|
||||||
const GREY_MODE = "grey-mode"
|
const GREY_MODE = "grey-mode"
|
||||||
const COLOR_WEAKNESS = "color-weakness"
|
const COLOR_WEAKNESS = "color-weakness"
|
||||||
const classList = document.documentElement.classList
|
const classList = document.documentElement.classList
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
const initGreyAndColorWeakness = () => {
|
function initGreyAndColorWeakness() {
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
classList.toggle(GREY_MODE, settingsStore.showGreyMode)
|
classList.toggle(GREY_MODE, settingsStore.showGreyMode)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { computed } from "vue"
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
|
||||||
import { LayoutModeEnum } from "@/constants/app-key"
|
import { LayoutModeEnum } from "@/constants/app-key"
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
const isLeft = computed(() => settingsStore.layoutMode === LayoutModeEnum.Left)
|
const isLeft = computed(() => settingsStore.layoutMode === LayoutModeEnum.Left)
|
||||||
const isTop = computed(() => settingsStore.layoutMode === LayoutModeEnum.Top)
|
const isTop = computed(() => settingsStore.layoutMode === LayoutModeEnum.Top)
|
||||||
const isLeftTop = computed(() => settingsStore.layoutMode === LayoutModeEnum.LeftTop)
|
const isLeftTop = computed(() => settingsStore.layoutMode === LayoutModeEnum.LeftTop)
|
||||||
|
|
||||||
const setLayoutMode = (mode: LayoutModeEnum) => {
|
function setLayoutMode(mode: LayoutModeEnum) {
|
||||||
settingsStore.layoutMode = mode
|
settingsStore.layoutMode = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { onBeforeUnmount } from "vue"
|
import type { RouteLocationNormalized } from "vue-router"
|
||||||
import mitt, { type Handler } from "mitt"
|
import mitt, { type Handler } from "mitt"
|
||||||
import { type RouteLocationNormalized } from "vue-router"
|
import { onBeforeUnmount } from "vue"
|
||||||
|
|
||||||
/** 回调函数的类型 */
|
/** 回调函数的类型 */
|
||||||
type Callback = (route: RouteLocationNormalized) => void
|
type Callback = (route: RouteLocationNormalized) => void
|
||||||
@ -10,7 +10,7 @@ const key = Symbol("ROUTE_CHANGE")
|
|||||||
let latestRoute: RouteLocationNormalized
|
let latestRoute: RouteLocationNormalized
|
||||||
|
|
||||||
/** 设置最新的路由信息,触发路由变化事件 */
|
/** 设置最新的路由信息,触发路由变化事件 */
|
||||||
export const setRouteChange = (to: RouteLocationNormalized) => {
|
export function setRouteChange(to: RouteLocationNormalized) {
|
||||||
// 触发事件
|
// 触发事件
|
||||||
emitter.emit(key, to)
|
emitter.emit(key, to)
|
||||||
// 缓存最新的路由信息
|
// 缓存最新的路由信息
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ref, watchEffect } from "vue"
|
|
||||||
import { getActiveThemeName, setActiveThemeName } from "@/utils/cache/local-storage"
|
import { getActiveThemeName, setActiveThemeName } from "@/utils/cache/local-storage"
|
||||||
|
import { ref, watchEffect } from "vue"
|
||||||
|
|
||||||
const DEFAULT_THEME_NAME = "normal"
|
const DEFAULT_THEME_NAME = "normal"
|
||||||
type DefaultThemeName = typeof DEFAULT_THEME_NAME
|
type DefaultThemeName = typeof DEFAULT_THEME_NAME
|
||||||
@ -32,23 +32,23 @@ const themeList: ThemeList[] = [
|
|||||||
const activeThemeName = ref<ThemeName>(getActiveThemeName() || DEFAULT_THEME_NAME)
|
const activeThemeName = ref<ThemeName>(getActiveThemeName() || DEFAULT_THEME_NAME)
|
||||||
|
|
||||||
/** 设置主题 */
|
/** 设置主题 */
|
||||||
const setTheme = (value: ThemeName) => {
|
function setTheme(value: ThemeName) {
|
||||||
activeThemeName.value = value
|
activeThemeName.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在 html 根元素上挂载 class */
|
/** 在 html 根元素上挂载 class */
|
||||||
const addHtmlClass = (value: ThemeName) => {
|
function addHtmlClass(value: ThemeName) {
|
||||||
document.documentElement.classList.add(value)
|
document.documentElement.classList.add(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在 html 根元素上移除其他主题 class */
|
/** 在 html 根元素上移除其他主题 class */
|
||||||
const removeHtmlClass = (value: ThemeName) => {
|
function removeHtmlClass(value: ThemeName) {
|
||||||
const otherThemeNameList = themeList.map((item) => item.name).filter((name) => name !== value)
|
const otherThemeNameList = themeList.map(item => item.name).filter(name => name !== value)
|
||||||
document.documentElement.classList.remove(...otherThemeNameList)
|
document.documentElement.classList.remove(...otherThemeNameList)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
const initTheme = () => {
|
function initTheme() {
|
||||||
// watchEffect 来收集副作用
|
// watchEffect 来收集副作用
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
const value = activeThemeName.value
|
const value = activeThemeName.value
|
||||||
|
@ -7,7 +7,7 @@ const VITE_APP_TITLE = import.meta.env.VITE_APP_TITLE ?? "V3 Admin Vite"
|
|||||||
const dynamicTitle = ref<string>("")
|
const dynamicTitle = ref<string>("")
|
||||||
|
|
||||||
/** 设置标题 */
|
/** 设置标题 */
|
||||||
const setTitle = (title?: string) => {
|
function setTitle(title?: string) {
|
||||||
dynamicTitle.value = title ? `${VITE_APP_TITLE} | ${title}` : VITE_APP_TITLE
|
dynamicTitle.value = title ? `${VITE_APP_TITLE} | ${title}` : VITE_APP_TITLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { type Ref, onBeforeUnmount, ref } from "vue"
|
|
||||||
import { debounce } from "lodash-es"
|
import { debounce } from "lodash-es"
|
||||||
|
import { onBeforeUnmount, type Ref, ref } from "vue"
|
||||||
|
|
||||||
type Observer = {
|
interface Observer {
|
||||||
watermarkElMutationObserver?: MutationObserver
|
watermarkElMutationObserver?: MutationObserver
|
||||||
parentElMutationObserver?: MutationObserver
|
parentElMutationObserver?: MutationObserver
|
||||||
parentElResizeObserver?: ResizeObserver
|
parentElResizeObserver?: ResizeObserver
|
||||||
@ -124,10 +124,12 @@ export function useWatermark(parentEl: Ref<HTMLElement | null> = bodyEl) {
|
|||||||
// 移除水印元素
|
// 移除水印元素
|
||||||
try {
|
try {
|
||||||
parentEl.value.removeChild(watermarkEl)
|
parentEl.value.removeChild(watermarkEl)
|
||||||
} catch {
|
}
|
||||||
|
catch {
|
||||||
// 比如在无防御情况下,用户打开控制台删除了这个元素
|
// 比如在无防御情况下,用户打开控制台删除了这个元素
|
||||||
console.warn("水印元素已不存在,请重新创建")
|
console.warn("水印元素已不存在,请重新创建")
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
watermarkEl = null
|
watermarkEl = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type App } from "vue"
|
import type { App } from "vue"
|
||||||
import SvgIcon from "@/components/SvgIcon/index.vue" // Svg Component
|
import SvgIcon from "@/components/SvgIcon/index.vue" // Svg Component
|
||||||
import "virtual:svg-icons-register"
|
import "virtual:svg-icons-register"
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
import { useDevice } from "@/hooks/useDevice"
|
||||||
import { storeToRefs } from "pinia"
|
|
||||||
import { useAppStore } from "@/store/modules/app"
|
import { useAppStore } from "@/store/modules/app"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { computed } from "vue"
|
||||||
import { AppMain, NavigationBar, Sidebar, TagsView } from "./components"
|
import { AppMain, NavigationBar, Sidebar, TagsView } from "./components"
|
||||||
import { useDevice } from "@/hooks/useDevice"
|
|
||||||
|
|
||||||
const { isMobile } = useDevice()
|
const { isMobile } = useDevice()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
@ -22,7 +22,7 @@ const layoutClasses = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
/** 用于处理点击 mobile 端侧边栏遮罩层的事件 */
|
/** 用于处理点击 mobile 端侧边栏遮罩层的事件 */
|
||||||
const handleClickOutside = () => {
|
function handleClickOutside() {
|
||||||
appStore.closeSidebar(false)
|
appStore.closeSidebar(false)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
|
||||||
import { storeToRefs } from "pinia"
|
|
||||||
import { useAppStore } from "@/store/modules/app"
|
import { useAppStore } from "@/store/modules/app"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { AppMain, NavigationBar, Sidebar, TagsView, Logo } from "./components"
|
import { storeToRefs } from "pinia"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { AppMain, Logo, NavigationBar, Sidebar, TagsView } from "./components"
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { storeToRefs } from "pinia"
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { AppMain, NavigationBar, TagsView, Logo } from "./components"
|
import { storeToRefs } from "pinia"
|
||||||
|
import { AppMain, Logo, NavigationBar, TagsView } from "./components"
|
||||||
|
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
const { showTagsView, showLogo } = storeToRefs(settingsStore)
|
const { showTagsView, showLogo } = storeToRefs(settingsStore)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTagsViewStore } from "@/store/modules/tags-view"
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
|
import { useTagsViewStore } from "@/store/modules/tags-view"
|
||||||
import Footer from "./Footer/index.vue"
|
import Footer from "./Footer/index.vue"
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore()
|
const tagsViewStore = useTagsViewStore()
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue"
|
import type { RouteLocationMatched } from "vue-router"
|
||||||
import { type RouteLocationMatched, useRoute, useRouter } from "vue-router"
|
|
||||||
import { useRouteListener } from "@/hooks/useRouteListener"
|
import { useRouteListener } from "@/hooks/useRouteListener"
|
||||||
import { compile } from "path-to-regexp"
|
import { compile } from "path-to-regexp"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { useRoute, useRouter } from "vue-router"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -12,18 +13,18 @@ const { listenerRouteChange } = useRouteListener()
|
|||||||
const breadcrumbs = ref<RouteLocationMatched[]>([])
|
const breadcrumbs = ref<RouteLocationMatched[]>([])
|
||||||
|
|
||||||
/** 获取面包屑导航信息 */
|
/** 获取面包屑导航信息 */
|
||||||
const getBreadcrumb = () => {
|
function getBreadcrumb() {
|
||||||
breadcrumbs.value = route.matched.filter((item) => item.meta?.title && item.meta?.breadcrumb !== false)
|
breadcrumbs.value = route.matched.filter(item => item.meta?.title && item.meta?.breadcrumb !== false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编译路由路径 */
|
/** 编译路由路径 */
|
||||||
const pathCompile = (path: string) => {
|
function pathCompile(path: string) {
|
||||||
const toPath = compile(path)
|
const toPath = compile(path)
|
||||||
return toPath(route.params)
|
return toPath(route.params)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理面包屑导航点击事件 */
|
/** 处理面包屑导航点击事件 */
|
||||||
const handleLink = (item: RouteLocationMatched) => {
|
function handleLink(item: RouteLocationMatched) {
|
||||||
const { redirect, path } = item
|
const { redirect, path } = item
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
router.push(redirect as string)
|
router.push(redirect as string)
|
||||||
|
@ -3,7 +3,9 @@ const VITE_APP_TITLE = import.meta.env.VITE_APP_TITLE
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<footer class="layout-footer">MIT © 2021-PRESENT {{ VITE_APP_TITLE }}</footer>
|
<footer class="layout-footer">
|
||||||
|
MIT © 2021-PRESENT {{ VITE_APP_TITLE }}
|
||||||
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -14,7 +14,7 @@ const emit = defineEmits<{
|
|||||||
toggleClick: []
|
toggleClick: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const toggleClick = () => {
|
function toggleClick() {
|
||||||
emit("toggleClick")
|
emit("toggleClick")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
|
||||||
import logo from "@/assets/layouts/logo.png?url"
|
import logo from "@/assets/layouts/logo.png?url"
|
||||||
import logoText1 from "@/assets/layouts/logo-text-1.png?url"
|
import logoText1 from "@/assets/layouts/logo-text-1.png?url"
|
||||||
import logoText2 from "@/assets/layouts/logo-text-2.png?url"
|
import logoText2 from "@/assets/layouts/logo-text-2.png?url"
|
||||||
|
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
collapse?: boolean
|
collapse?: boolean
|
||||||
@ -16,13 +16,13 @@ const { isLeft, isTop } = useLayoutMode()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout-logo-container" :class="{ collapse: props.collapse, 'layout-mode-top': isTop }">
|
<div class="layout-logo-container" :class="{ 'collapse': props.collapse, 'layout-mode-top': isTop }">
|
||||||
<transition name="layout-logo-fade">
|
<transition name="layout-logo-fade">
|
||||||
<router-link v-if="props.collapse" key="collapse" to="/">
|
<router-link v-if="props.collapse" key="collapse" to="/">
|
||||||
<img :src="logo" class="layout-logo" />
|
<img :src="logo" class="layout-logo">
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link v-else key="expand" to="/">
|
<router-link v-else key="expand" to="/">
|
||||||
<img :src="!isLeft ? logoText2 : logoText1" class="layout-logo-text" />
|
<img :src="!isLeft ? logoText2 : logoText1" class="layout-logo-text">
|
||||||
</router-link>
|
</router-link>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRouter } from "vue-router"
|
import Notify from "@/components/Notify/index.vue"
|
||||||
import { storeToRefs } from "pinia"
|
import Screenfull from "@/components/Screenfull/index.vue"
|
||||||
|
import SearchMenu from "@/components/SearchMenu/index.vue"
|
||||||
|
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
||||||
|
import { useDevice } from "@/hooks/useDevice"
|
||||||
|
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
||||||
import { useAppStore } from "@/store/modules/app"
|
import { useAppStore } from "@/store/modules/app"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { useUserStore } from "@/store/modules/user"
|
import { useUserStore } from "@/store/modules/user"
|
||||||
import { UserFilled } from "@element-plus/icons-vue"
|
import { UserFilled } from "@element-plus/icons-vue"
|
||||||
import Hamburger from "../Hamburger/index.vue"
|
import { storeToRefs } from "pinia"
|
||||||
|
import { useRouter } from "vue-router"
|
||||||
import Breadcrumb from "../Breadcrumb/index.vue"
|
import Breadcrumb from "../Breadcrumb/index.vue"
|
||||||
|
import Hamburger from "../Hamburger/index.vue"
|
||||||
import Sidebar from "../Sidebar/index.vue"
|
import Sidebar from "../Sidebar/index.vue"
|
||||||
import Notify from "@/components/Notify/index.vue"
|
|
||||||
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
|
||||||
import Screenfull from "@/components/Screenfull/index.vue"
|
|
||||||
import SearchMenu from "@/components/SearchMenu/index.vue"
|
|
||||||
import { useDevice } from "@/hooks/useDevice"
|
|
||||||
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
|
||||||
|
|
||||||
const { isMobile } = useDevice()
|
const { isMobile } = useDevice()
|
||||||
const { isTop } = useLayoutMode()
|
const { isTop } = useLayoutMode()
|
||||||
@ -24,12 +24,12 @@ const settingsStore = useSettingsStore()
|
|||||||
const { showNotify, showThemeSwitch, showScreenfull, showSearchMenu } = storeToRefs(settingsStore)
|
const { showNotify, showThemeSwitch, showScreenfull, showSearchMenu } = storeToRefs(settingsStore)
|
||||||
|
|
||||||
/** 切换侧边栏 */
|
/** 切换侧边栏 */
|
||||||
const toggleSidebar = () => {
|
function toggleSidebar() {
|
||||||
appStore.toggleSidebar(false)
|
appStore.toggleSidebar(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登出 */
|
/** 登出 */
|
||||||
const logout = () => {
|
function logout() {
|
||||||
userStore.logout()
|
userStore.logout()
|
||||||
router.push("/login")
|
router.push("/login")
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue"
|
|
||||||
import { Setting } from "@element-plus/icons-vue"
|
import { Setting } from "@element-plus/icons-vue"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
buttonTop?: number
|
buttonTop?: number
|
||||||
@ -10,7 +10,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
buttonTop: 350
|
buttonTop: 350
|
||||||
})
|
})
|
||||||
|
|
||||||
const buttonTopCss = props.buttonTop + "px"
|
const buttonTopCss = `${props.buttonTop}px`
|
||||||
const show = ref(false)
|
const show = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
|
||||||
import { LayoutModeEnum } from "@/constants/app-key"
|
import { LayoutModeEnum } from "@/constants/app-key"
|
||||||
|
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
||||||
|
|
||||||
const { isLeft, isTop, isLeftTop, setLayoutMode } = useLayoutMode()
|
const { isLeft, isTop, isLeftTop, setLayoutMode } = useLayoutMode()
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watchEffect } from "vue"
|
|
||||||
import { storeToRefs } from "pinia"
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
|
||||||
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { removeConfigLayout } from "@/utils/cache/local-storage"
|
import { removeConfigLayout } from "@/utils/cache/local-storage"
|
||||||
import SelectLayoutMode from "./SelectLayoutMode.vue"
|
|
||||||
import { Refresh } from "@element-plus/icons-vue"
|
import { Refresh } from "@element-plus/icons-vue"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { watchEffect } from "vue"
|
||||||
|
import SelectLayoutMode from "./SelectLayoutMode.vue"
|
||||||
|
|
||||||
const { isLeft } = useLayoutMode()
|
const { isLeft } = useLayoutMode()
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
@ -28,18 +28,18 @@ const {
|
|||||||
|
|
||||||
/** 定义 switch 设置项 */
|
/** 定义 switch 设置项 */
|
||||||
const switchSettings = {
|
const switchSettings = {
|
||||||
显示标签栏: showTagsView,
|
"显示标签栏": showTagsView,
|
||||||
"显示 Logo": showLogo,
|
"显示 Logo": showLogo,
|
||||||
"固定 Header": fixedHeader,
|
"固定 Header": fixedHeader,
|
||||||
"显示页脚 Footer": showFooter,
|
"显示页脚 Footer": showFooter,
|
||||||
显示消息通知: showNotify,
|
"显示消息通知": showNotify,
|
||||||
显示切换主题按钮: showThemeSwitch,
|
"显示切换主题按钮": showThemeSwitch,
|
||||||
显示全屏按钮: showScreenfull,
|
"显示全屏按钮": showScreenfull,
|
||||||
显示搜索按钮: showSearchMenu,
|
"显示搜索按钮": showSearchMenu,
|
||||||
是否缓存标签栏: cacheTagsView,
|
"是否缓存标签栏": cacheTagsView,
|
||||||
开启系统水印: showWatermark,
|
"开启系统水印": showWatermark,
|
||||||
显示灰色模式: showGreyMode,
|
"显示灰色模式": showGreyMode,
|
||||||
显示色弱模式: showColorWeakness
|
"显示色弱模式": showColorWeakness
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 非左侧模式时,Header 都是 fixed 布局 */
|
/** 非左侧模式时,Header 都是 fixed 布局 */
|
||||||
@ -48,7 +48,7 @@ watchEffect(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
/** 重置项目配置 */
|
/** 重置项目配置 */
|
||||||
const resetConfigLayout = () => {
|
function resetConfigLayout() {
|
||||||
removeConfigLayout()
|
removeConfigLayout()
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
@ -60,11 +60,13 @@ const resetConfigLayout = () => {
|
|||||||
<SelectLayoutMode />
|
<SelectLayoutMode />
|
||||||
<el-divider />
|
<el-divider />
|
||||||
<h4>功能配置</h4>
|
<h4>功能配置</h4>
|
||||||
<div class="setting-item" v-for="(settingValue, settingName, index) in switchSettings" :key="index">
|
<div v-for="(settingValue, settingName, index) in switchSettings" :key="index" class="setting-item">
|
||||||
<span class="setting-name">{{ settingName }}</span>
|
<span class="setting-name">{{ settingName }}</span>
|
||||||
<el-switch v-model="settingValue.value" :disabled="!isLeft && settingName === '固定 Header'" />
|
<el-switch v-model="settingValue.value" :disabled="!isLeft && settingName === '固定 Header'" />
|
||||||
</div>
|
</div>
|
||||||
<el-button type="danger" :icon="Refresh" @click="resetConfigLayout">重 置</el-button>
|
<el-button type="danger" :icon="Refresh" @click="resetConfigLayout">
|
||||||
|
重 置
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
import type { RouteRecordRaw } from "vue-router"
|
||||||
import { type RouteRecordRaw } from "vue-router"
|
|
||||||
import SidebarItemLink from "./SidebarItemLink.vue"
|
|
||||||
import { isExternal } from "@/utils/validate"
|
import { isExternal } from "@/utils/validate"
|
||||||
import path from "path-browserify"
|
import path from "path-browserify"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import SidebarItemLink from "./SidebarItemLink.vue"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: RouteRecordRaw
|
item: RouteRecordRaw
|
||||||
@ -19,7 +19,7 @@ const alwaysShowRootMenu = computed(() => props.item.meta?.alwaysShow)
|
|||||||
|
|
||||||
/** 显示的子菜单 */
|
/** 显示的子菜单 */
|
||||||
const showingChildren = computed(() => {
|
const showingChildren = computed(() => {
|
||||||
return props.item.children?.filter((child) => !child.meta?.hidden) ?? []
|
return props.item.children?.filter(child => !child.meta?.hidden) ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 显示的子菜单数量 */
|
/** 显示的子菜单数量 */
|
||||||
@ -41,7 +41,7 @@ const theOnlyOneChild = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
/** 解析路径 */
|
/** 解析路径 */
|
||||||
const resolvePath = (routePath: string) => {
|
function resolvePath(routePath: string) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isExternal(routePath):
|
case isExternal(routePath):
|
||||||
return routePath
|
return routePath
|
||||||
@ -58,7 +58,7 @@ const resolvePath = (routePath: string) => {
|
|||||||
<SidebarItemLink v-if="theOnlyOneChild.meta" :to="resolvePath(theOnlyOneChild.path)">
|
<SidebarItemLink v-if="theOnlyOneChild.meta" :to="resolvePath(theOnlyOneChild.path)">
|
||||||
<el-menu-item :index="resolvePath(theOnlyOneChild.path)">
|
<el-menu-item :index="resolvePath(theOnlyOneChild.path)">
|
||||||
<SvgIcon v-if="theOnlyOneChild.meta.svgIcon" :name="theOnlyOneChild.meta.svgIcon" />
|
<SvgIcon v-if="theOnlyOneChild.meta.svgIcon" :name="theOnlyOneChild.meta.svgIcon" />
|
||||||
<component v-else-if="theOnlyOneChild.meta.elIcon" :is="theOnlyOneChild.meta.elIcon" class="el-icon" />
|
<component :is="theOnlyOneChild.meta.elIcon" v-else-if="theOnlyOneChild.meta.elIcon" class="el-icon" />
|
||||||
<template v-if="theOnlyOneChild.meta.title" #title>
|
<template v-if="theOnlyOneChild.meta.title" #title>
|
||||||
{{ theOnlyOneChild.meta.title }}
|
{{ theOnlyOneChild.meta.title }}
|
||||||
</template>
|
</template>
|
||||||
@ -68,7 +68,7 @@ const resolvePath = (routePath: string) => {
|
|||||||
<el-sub-menu v-else :index="resolvePath(props.item.path)" teleported>
|
<el-sub-menu v-else :index="resolvePath(props.item.path)" teleported>
|
||||||
<template #title>
|
<template #title>
|
||||||
<SvgIcon v-if="props.item.meta?.svgIcon" :name="props.item.meta.svgIcon" />
|
<SvgIcon v-if="props.item.meta?.svgIcon" :name="props.item.meta.svgIcon" />
|
||||||
<component v-else-if="props.item.meta?.elIcon" :is="props.item.meta.elIcon" class="el-icon" />
|
<component :is="props.item.meta.elIcon" v-else-if="props.item.meta?.elIcon" class="el-icon" />
|
||||||
<span v-if="props.item.meta?.title">{{ props.item.meta.title }}</span>
|
<span v-if="props.item.meta?.title">{{ props.item.meta.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="props.item.children">
|
<template v-if="props.item.children">
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue"
|
import { useDevice } from "@/hooks/useDevice"
|
||||||
import { useRoute } from "vue-router"
|
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
||||||
import { useAppStore } from "@/store/modules/app"
|
import { useAppStore } from "@/store/modules/app"
|
||||||
import { usePermissionStore } from "@/store/modules/permission"
|
import { usePermissionStore } from "@/store/modules/permission"
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import SidebarItem from "./SidebarItem.vue"
|
|
||||||
import Logo from "../Logo/index.vue"
|
|
||||||
import { useDevice } from "@/hooks/useDevice"
|
|
||||||
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
|
||||||
import { getCssVar } from "@/utils/css"
|
import { getCssVar } from "@/utils/css"
|
||||||
|
import { computed } from "vue"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
import Logo from "../Logo/index.vue"
|
||||||
|
import SidebarItem from "./SidebarItem.vue"
|
||||||
|
|
||||||
const v3SidebarMenuBgColor = getCssVar("--v3-sidebar-menu-bg-color")
|
const v3SidebarMenuBgColor = getCssVar("--v3-sidebar-menu-bg-color")
|
||||||
const v3SidebarMenuTextColor = getCssVar("--v3-sidebar-menu-text-color")
|
const v3SidebarMenuTextColor = getCssVar("--v3-sidebar-menu-text-color")
|
||||||
@ -26,9 +26,9 @@ const activeMenu = computed(() => {
|
|||||||
meta: { activeMenu },
|
meta: { activeMenu },
|
||||||
path
|
path
|
||||||
} = route
|
} = route
|
||||||
return activeMenu ? activeMenu : path
|
return activeMenu || path
|
||||||
})
|
})
|
||||||
const noHiddenRoutes = computed(() => permissionStore.routes.filter((item) => !item.meta?.hidden))
|
const noHiddenRoutes = computed(() => permissionStore.routes.filter(item => !item.meta?.hidden))
|
||||||
const isCollapse = computed(() => !appStore.sidebar.opened)
|
const isCollapse = computed(() => !appStore.sidebar.opened)
|
||||||
const isLogo = computed(() => isLeft.value && settingsStore.showLogo)
|
const isLogo = computed(() => isLeft.value && settingsStore.showLogo)
|
||||||
const backgroundColor = computed(() => (isLeft.value ? v3SidebarMenuBgColor : undefined))
|
const backgroundColor = computed(() => (isLeft.value ? v3SidebarMenuBgColor : undefined))
|
||||||
@ -63,7 +63,12 @@ const hiddenScrollbarVerticalBar = computed(() => {
|
|||||||
:collapse-transition="false"
|
:collapse-transition="false"
|
||||||
:mode="isTop && !isMobile ? 'horizontal' : 'vertical'"
|
:mode="isTop && !isMobile ? 'horizontal' : 'vertical'"
|
||||||
>
|
>
|
||||||
<SidebarItem v-for="route in noHiddenRoutes" :key="route.path" :item="route" :base-path="route.path" />
|
<SidebarItem
|
||||||
|
v-for="noHiddenRoute in noHiddenRoutes"
|
||||||
|
:key="noHiddenRoute.path"
|
||||||
|
:item="noHiddenRoute"
|
||||||
|
:base-path="noHiddenRoute.path"
|
||||||
|
/>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, nextTick } from "vue"
|
import type { RouterLink } from "vue-router"
|
||||||
import { RouterLink, useRoute } from "vue-router"
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
|
||||||
import { useRouteListener } from "@/hooks/useRouteListener"
|
|
||||||
import Screenfull from "@/components/Screenfull/index.vue"
|
import Screenfull from "@/components/Screenfull/index.vue"
|
||||||
import { ElScrollbar } from "element-plus"
|
import { useRouteListener } from "@/hooks/useRouteListener"
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue"
|
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue"
|
||||||
|
import { ElScrollbar } from "element-plus"
|
||||||
|
import { nextTick, ref } from "vue"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tagRefs: InstanceType<typeof RouterLink>[]
|
tagRefs: InstanceType<typeof RouterLink>[]
|
||||||
@ -28,13 +29,13 @@ let currentScrollLeft = 0
|
|||||||
const translateDistance = 200
|
const translateDistance = 200
|
||||||
|
|
||||||
/** 滚动时触发 */
|
/** 滚动时触发 */
|
||||||
const scroll = ({ scrollLeft }: { scrollLeft: number }) => {
|
function scroll({ scrollLeft }: { scrollLeft: number }) {
|
||||||
currentScrollLeft = scrollLeft
|
currentScrollLeft = scrollLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 鼠标滚轮滚动时触发 */
|
/** 鼠标滚轮滚动时触发 */
|
||||||
const wheelScroll = ({ deltaY }: WheelEvent) => {
|
function wheelScroll({ deltaY }: WheelEvent) {
|
||||||
if (/^-/.test(deltaY.toString())) {
|
if (deltaY.toString().startsWith("-")) {
|
||||||
scrollTo("left")
|
scrollTo("left")
|
||||||
} else {
|
} else {
|
||||||
scrollTo("right")
|
scrollTo("right")
|
||||||
@ -42,7 +43,7 @@ const wheelScroll = ({ deltaY }: WheelEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 获取可能需要的宽度 */
|
/** 获取可能需要的宽度 */
|
||||||
const getWidth = () => {
|
function getWidth() {
|
||||||
/** 可滚动内容的长度 */
|
/** 可滚动内容的长度 */
|
||||||
const scrollbarContentRefWidth = scrollbarContentRef.value!.clientWidth
|
const scrollbarContentRefWidth = scrollbarContentRef.value!.clientWidth
|
||||||
/** 滚动可视区宽度 */
|
/** 滚动可视区宽度 */
|
||||||
@ -54,7 +55,7 @@ const getWidth = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 左右滚动 */
|
/** 左右滚动 */
|
||||||
const scrollTo = (direction: "left" | "right", distance: number = translateDistance) => {
|
function scrollTo(direction: "left" | "right", distance: number = translateDistance) {
|
||||||
let scrollLeft = 0
|
let scrollLeft = 0
|
||||||
const { scrollbarContentRefWidth, scrollbarRefWidth, lastDistance } = getWidth()
|
const { scrollbarContentRefWidth, scrollbarRefWidth, lastDistance } = getWidth()
|
||||||
// 没有横向滚动条,直接结束
|
// 没有横向滚动条,直接结束
|
||||||
@ -68,12 +69,12 @@ const scrollTo = (direction: "left" | "right", distance: number = translateDista
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 移动到目标位置 */
|
/** 移动到目标位置 */
|
||||||
const moveTo = () => {
|
function moveTo() {
|
||||||
const tagRefs = props.tagRefs
|
const tagRefs = props.tagRefs
|
||||||
for (let i = 0; i < tagRefs.length; i++) {
|
for (let i = 0; i < tagRefs.length; i++) {
|
||||||
// @ts-ignore
|
// @ts-expect-error ignore
|
||||||
if (route.path === tagRefs[i].$props.to.path) {
|
if (route.path === tagRefs[i].$props.to.path) {
|
||||||
// @ts-ignore
|
// @ts-expect-error ignore
|
||||||
const el: HTMLElement = tagRefs[i].$el
|
const el: HTMLElement = tagRefs[i].$el
|
||||||
const offsetWidth = el.offsetWidth
|
const offsetWidth = el.offsetWidth
|
||||||
const offsetLeft = el.offsetLeft
|
const offsetLeft = el.offsetLeft
|
||||||
@ -106,11 +107,11 @@ listenerRouteChange(() => {
|
|||||||
<el-icon class="arrow left" @click="scrollTo('left')">
|
<el-icon class="arrow left" @click="scrollTo('left')">
|
||||||
<ArrowLeft />
|
<ArrowLeft />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<el-scrollbar ref="scrollbarRef" @wheel.passive="wheelScroll" @scroll="scroll">
|
<ElScrollbar ref="scrollbarRef" @wheel.passive="wheelScroll" @scroll="scroll">
|
||||||
<div ref="scrollbarContentRef" class="scrollbar-content">
|
<div ref="scrollbarContentRef" class="scrollbar-content">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</ElScrollbar>
|
||||||
<el-icon class="arrow right" @click="scrollTo('right')">
|
<el-icon class="arrow right" @click="scrollTo('right')">
|
||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from "vue"
|
import type { TagView } from "@/store/modules/tags-view"
|
||||||
import { type RouteLocationNormalizedLoaded, type RouteRecordRaw, RouterLink, useRoute, useRouter } from "vue-router"
|
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from "vue-router"
|
||||||
import { type TagView, useTagsViewStore } from "@/store/modules/tags-view"
|
|
||||||
import { usePermissionStore } from "@/store/modules/permission"
|
|
||||||
import { useRouteListener } from "@/hooks/useRouteListener"
|
import { useRouteListener } from "@/hooks/useRouteListener"
|
||||||
import path from "path-browserify"
|
import { usePermissionStore } from "@/store/modules/permission"
|
||||||
import ScrollPane from "./ScrollPane.vue"
|
import { useTagsViewStore } from "@/store/modules/tags-view"
|
||||||
import { Close } from "@element-plus/icons-vue"
|
import { Close } from "@element-plus/icons-vue"
|
||||||
|
import path from "path-browserify"
|
||||||
|
import { ref, watch } from "vue"
|
||||||
|
import { RouterLink, useRoute, useRouter } from "vue-router"
|
||||||
|
import ScrollPane from "./ScrollPane.vue"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -29,17 +31,17 @@ const selectedTag = ref<TagView>({})
|
|||||||
let affixTags: TagView[] = []
|
let affixTags: TagView[] = []
|
||||||
|
|
||||||
/** 判断标签页是否激活 */
|
/** 判断标签页是否激活 */
|
||||||
const isActive = (tag: TagView) => {
|
function isActive(tag: TagView) {
|
||||||
return tag.path === route.path
|
return tag.path === route.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断标签页是否固定 */
|
/** 判断标签页是否固定 */
|
||||||
const isAffix = (tag: TagView) => {
|
function isAffix(tag: TagView) {
|
||||||
return tag.meta?.affix
|
return tag.meta?.affix
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 筛选出固定标签页 */
|
/** 筛选出固定标签页 */
|
||||||
const filterAffixTags = (routes: RouteRecordRaw[], basePath = "/") => {
|
function filterAffixTags(routes: RouteRecordRaw[], basePath = "/") {
|
||||||
const tags: TagView[] = []
|
const tags: TagView[] = []
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
if (isAffix(route)) {
|
if (isAffix(route)) {
|
||||||
@ -60,7 +62,7 @@ const filterAffixTags = (routes: RouteRecordRaw[], basePath = "/") => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化标签页 */
|
/** 初始化标签页 */
|
||||||
const initTags = () => {
|
function initTags() {
|
||||||
affixTags = filterAffixTags(permissionStore.routes)
|
affixTags = filterAffixTags(permissionStore.routes)
|
||||||
for (const tag of affixTags) {
|
for (const tag of affixTags) {
|
||||||
// 必须含有 name 属性
|
// 必须含有 name 属性
|
||||||
@ -69,7 +71,7 @@ const initTags = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 添加标签页 */
|
/** 添加标签页 */
|
||||||
const addTags = (route: RouteLocationNormalizedLoaded) => {
|
function addTags(route: RouteLocationNormalizedLoaded) {
|
||||||
if (route.name) {
|
if (route.name) {
|
||||||
tagsViewStore.addVisitedView(route)
|
tagsViewStore.addVisitedView(route)
|
||||||
tagsViewStore.addCachedView(route)
|
tagsViewStore.addCachedView(route)
|
||||||
@ -77,20 +79,20 @@ const addTags = (route: RouteLocationNormalizedLoaded) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新当前正在右键操作的标签页 */
|
/** 刷新当前正在右键操作的标签页 */
|
||||||
const refreshSelectedTag = (view: TagView) => {
|
function refreshSelectedTag(view: TagView) {
|
||||||
tagsViewStore.delCachedView(view)
|
tagsViewStore.delCachedView(view)
|
||||||
router.replace({ path: "/redirect" + view.path, query: view.query })
|
router.replace({ path: `/redirect${view.path}`, query: view.query })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭当前正在右键操作的标签页 */
|
/** 关闭当前正在右键操作的标签页 */
|
||||||
const closeSelectedTag = (view: TagView) => {
|
function closeSelectedTag(view: TagView) {
|
||||||
tagsViewStore.delVisitedView(view)
|
tagsViewStore.delVisitedView(view)
|
||||||
tagsViewStore.delCachedView(view)
|
tagsViewStore.delCachedView(view)
|
||||||
isActive(view) && toLastView(tagsViewStore.visitedViews, view)
|
isActive(view) && toLastView(tagsViewStore.visitedViews, view)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭其他标签页 */
|
/** 关闭其他标签页 */
|
||||||
const closeOthersTags = () => {
|
function closeOthersTags() {
|
||||||
const fullPath = selectedTag.value.fullPath
|
const fullPath = selectedTag.value.fullPath
|
||||||
if (fullPath !== route.path && fullPath !== undefined) {
|
if (fullPath !== route.path && fullPath !== undefined) {
|
||||||
router.push(fullPath)
|
router.push(fullPath)
|
||||||
@ -100,15 +102,15 @@ const closeOthersTags = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭所有标签页 */
|
/** 关闭所有标签页 */
|
||||||
const closeAllTags = (view: TagView) => {
|
function closeAllTags(view: TagView) {
|
||||||
tagsViewStore.delAllVisitedViews()
|
tagsViewStore.delAllVisitedViews()
|
||||||
tagsViewStore.delAllCachedViews()
|
tagsViewStore.delAllCachedViews()
|
||||||
if (affixTags.some((tag) => tag.path === route.path)) return
|
if (affixTags.some(tag => tag.path === route.path)) return
|
||||||
toLastView(tagsViewStore.visitedViews, view)
|
toLastView(tagsViewStore.visitedViews, view)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳转到最后一个标签页 */
|
/** 跳转到最后一个标签页 */
|
||||||
const toLastView = (visitedViews: TagView[], view: TagView) => {
|
function toLastView(visitedViews: TagView[], view: TagView) {
|
||||||
const latestView = visitedViews.slice(-1)[0]
|
const latestView = visitedViews.slice(-1)[0]
|
||||||
const fullPath = latestView?.fullPath
|
const fullPath = latestView?.fullPath
|
||||||
if (fullPath !== undefined) {
|
if (fullPath !== undefined) {
|
||||||
@ -117,7 +119,7 @@ const toLastView = (visitedViews: TagView[], view: TagView) => {
|
|||||||
// 如果 TagsView 全部被关闭了,则默认重定向到主页
|
// 如果 TagsView 全部被关闭了,则默认重定向到主页
|
||||||
if (view.name === "Dashboard") {
|
if (view.name === "Dashboard") {
|
||||||
// 重新加载主页
|
// 重新加载主页
|
||||||
router.push({ path: "/redirect" + view.path, query: view.query })
|
router.push({ path: `/redirect${view.path}`, query: view.query })
|
||||||
} else {
|
} else {
|
||||||
router.push("/")
|
router.push("/")
|
||||||
}
|
}
|
||||||
@ -125,7 +127,7 @@ const toLastView = (visitedViews: TagView[], view: TagView) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 打开右键菜单面板 */
|
/** 打开右键菜单面板 */
|
||||||
const openMenu = (tag: TagView, e: MouseEvent) => {
|
function openMenu(tag: TagView, e: MouseEvent) {
|
||||||
const menuMinWidth = 100
|
const menuMinWidth = 100
|
||||||
// 当前页面宽度
|
// 当前页面宽度
|
||||||
const offsetWidth = document.body.offsetWidth
|
const offsetWidth = document.body.offsetWidth
|
||||||
@ -142,7 +144,7 @@ const openMenu = (tag: TagView, e: MouseEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭右键菜单面板 */
|
/** 关闭右键菜单面板 */
|
||||||
const closeMenu = () => {
|
function closeMenu() {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,9 +163,9 @@ listenerRouteChange((route) => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="tags-view-container">
|
<div class="tags-view-container">
|
||||||
<ScrollPane class="tags-view-wrapper" :tag-refs="tagRefs">
|
<ScrollPane class="tags-view-wrapper" :tag-refs="tagRefs">
|
||||||
<router-link
|
<RouterLink
|
||||||
ref="tagRefs"
|
|
||||||
v-for="tag in tagsViewStore.visitedViews"
|
v-for="tag in tagsViewStore.visitedViews"
|
||||||
|
ref="tagRefs"
|
||||||
:key="tag.path"
|
:key="tag.path"
|
||||||
:class="{ active: isActive(tag) }"
|
:class="{ active: isActive(tag) }"
|
||||||
class="tags-view-item"
|
class="tags-view-item"
|
||||||
@ -175,13 +177,21 @@ listenerRouteChange((route) => {
|
|||||||
<el-icon v-if="!isAffix(tag)" :size="12" @click.prevent.stop="closeSelectedTag(tag)">
|
<el-icon v-if="!isAffix(tag)" :size="12" @click.prevent.stop="closeSelectedTag(tag)">
|
||||||
<Close />
|
<Close />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</router-link>
|
</RouterLink>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
<ul v-show="visible" class="contextmenu" :style="{ left: left + 'px', top: top + 'px' }">
|
<ul v-show="visible" class="contextmenu" :style="{ left: `${left}px`, top: `${top}px` }">
|
||||||
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
|
<li @click="refreshSelectedTag(selectedTag)">
|
||||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
|
刷新
|
||||||
<li @click="closeOthersTags">关闭其它</li>
|
</li>
|
||||||
<li @click="closeAllTags(selectedTag)">关闭所有</li>
|
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
||||||
|
关闭
|
||||||
|
</li>
|
||||||
|
<li @click="closeOthersTags">
|
||||||
|
关闭其它
|
||||||
|
</li>
|
||||||
|
<li @click="closeAllTags(selectedTag)">
|
||||||
|
关闭所有
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export { default as AppMain } from "./AppMain.vue"
|
export { default as AppMain } from "./AppMain.vue"
|
||||||
|
export { default as Logo } from "./Logo/index.vue"
|
||||||
export { default as NavigationBar } from "./NavigationBar/index.vue"
|
export { default as NavigationBar } from "./NavigationBar/index.vue"
|
||||||
|
export { default as RightPanel } from "./RightPanel/index.vue"
|
||||||
export { default as Settings } from "./Settings/index.vue"
|
export { default as Settings } from "./Settings/index.vue"
|
||||||
export { default as Sidebar } from "./Sidebar/index.vue"
|
export { default as Sidebar } from "./Sidebar/index.vue"
|
||||||
export { default as TagsView } from "./TagsView/index.vue"
|
export { default as TagsView } from "./TagsView/index.vue"
|
||||||
export { default as RightPanel } from "./RightPanel/index.vue"
|
|
||||||
export { default as Logo } from "./Logo/index.vue"
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { onBeforeMount, onMounted, onBeforeUnmount } from "vue"
|
|
||||||
import { useAppStore } from "@/store/modules/app"
|
|
||||||
import { useRouteListener } from "@/hooks/useRouteListener"
|
|
||||||
import { DeviceEnum } from "@/constants/app-key"
|
import { DeviceEnum } from "@/constants/app-key"
|
||||||
|
import { useRouteListener } from "@/hooks/useRouteListener"
|
||||||
|
import { useAppStore } from "@/store/modules/app"
|
||||||
|
import { onBeforeMount, onBeforeUnmount, onMounted } from "vue"
|
||||||
|
|
||||||
/** 参考 Bootstrap 的响应式设计将最大移动端宽度设置为 992 */
|
/** 参考 Bootstrap 的响应式设计将最大移动端宽度设置为 992 */
|
||||||
const MAX_MOBILE_WIDTH = 992
|
const MAX_MOBILE_WIDTH = 992
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { watchEffect } from "vue"
|
|
||||||
import { storeToRefs } from "pinia"
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings"
|
|
||||||
import useResize from "./hooks/useResize"
|
|
||||||
import { useWatermark } from "@/hooks/useWatermark"
|
|
||||||
import { useDevice } from "@/hooks/useDevice"
|
import { useDevice } from "@/hooks/useDevice"
|
||||||
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
import { useLayoutMode } from "@/hooks/useLayoutMode"
|
||||||
import LeftMode from "./LeftMode.vue"
|
import { useWatermark } from "@/hooks/useWatermark"
|
||||||
import TopMode from "./TopMode.vue"
|
import { useSettingsStore } from "@/store/modules/settings"
|
||||||
import LeftTopMode from "./LeftTopMode.vue"
|
|
||||||
import { Settings, RightPanel } from "./components"
|
|
||||||
import { getCssVar, setCssVar } from "@/utils/css"
|
import { getCssVar, setCssVar } from "@/utils/css"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { watchEffect } from "vue"
|
||||||
|
import { RightPanel, Settings } from "./components"
|
||||||
|
import useResize from "./hooks/useResize"
|
||||||
|
import LeftMode from "./LeftMode.vue"
|
||||||
|
import LeftTopMode from "./LeftTopMode.vue"
|
||||||
|
import TopMode from "./TopMode.vue"
|
||||||
|
|
||||||
/** Layout 布局响应式 */
|
/** Layout 布局响应式 */
|
||||||
useResize()
|
useResize()
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// core
|
// core
|
||||||
import App from "@/App.vue"
|
import App from "@/App.vue"
|
||||||
import { createApp } from "vue"
|
import { loadDirectives } from "@/directives"
|
||||||
import { pinia } from "@/store"
|
|
||||||
import { router } from "@/router"
|
import { router } from "@/router"
|
||||||
|
import { pinia } from "@/store"
|
||||||
|
import { createApp } from "vue"
|
||||||
import "@/router/permission"
|
import "@/router/permission"
|
||||||
// load
|
// load
|
||||||
import { loadSvg } from "@/icons"
|
import { loadSvg } from "@/icons"
|
||||||
import { loadPlugins } from "@/plugins"
|
import { loadPlugins } from "@/plugins"
|
||||||
import { loadDirectives } from "@/directives"
|
|
||||||
// css
|
// css
|
||||||
import "uno.css"
|
import "uno.css"
|
||||||
import "normalize.css"
|
import "normalize.css"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type App } from "vue"
|
import type { App } from "vue"
|
||||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue"
|
import * as ElementPlusIconsVue from "@element-plus/icons-vue"
|
||||||
|
|
||||||
export function loadElementPlusIcon(app: App) {
|
export function loadElementPlusIcon(app: App) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type App } from "vue"
|
import type { App } from "vue"
|
||||||
import ElementPlus from "element-plus"
|
import ElementPlus from "element-plus"
|
||||||
|
|
||||||
export function loadElementPlus(app: App) {
|
export function loadElementPlus(app: App) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type App } from "vue"
|
import type { App } from "vue"
|
||||||
import { loadElementPlus } from "./element-plus"
|
import { loadElementPlus } from "./element-plus"
|
||||||
import { loadElementPlusIcon } from "./element-plus-icon"
|
import { loadElementPlusIcon } from "./element-plus-icon"
|
||||||
import { loadVxeTable } from "./vxe-table"
|
import { loadVxeTable } from "./vxe-table"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type App } from "vue"
|
import type { App } from "vue"
|
||||||
// https://vxetable.cn/#/table/start/install
|
// https://vxetable.cn/#/table/start/install
|
||||||
import VXETable from "vxe-table"
|
import VXETable from "vxe-table"
|
||||||
// https://github.com/x-extends/vxe-table-plugin-element
|
// https://github.com/x-extends/vxe-table-plugin-element
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
|
import { cloneDeep, omit } from "lodash-es"
|
||||||
import {
|
import {
|
||||||
type Router,
|
|
||||||
type RouteRecordNormalized,
|
|
||||||
type RouteRecordRaw,
|
|
||||||
createRouter,
|
createRouter,
|
||||||
createWebHashHistory,
|
createWebHashHistory,
|
||||||
createWebHistory
|
createWebHistory,
|
||||||
|
type Router,
|
||||||
|
type RouteRecordNormalized,
|
||||||
|
type RouteRecordRaw
|
||||||
} from "vue-router"
|
} from "vue-router"
|
||||||
import { cloneDeep, omit } from "lodash-es"
|
|
||||||
|
|
||||||
/** 路由模式 */
|
/** 路由模式 */
|
||||||
export const history =
|
export const history
|
||||||
import.meta.env.VITE_ROUTER_HISTORY === "hash"
|
= import.meta.env.VITE_ROUTER_HISTORY === "hash"
|
||||||
? createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH)
|
? createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH)
|
||||||
: createWebHistory(import.meta.env.VITE_PUBLIC_PATH)
|
: createWebHistory(import.meta.env.VITE_PUBLIC_PATH)
|
||||||
|
|
||||||
/** 路由降级(把三级及其以上的路由转化为二级路由) */
|
/** 路由降级(把三级及其以上的路由转化为二级路由) */
|
||||||
export const flatMultiLevelRoutes = (routes: RouteRecordRaw[]) => {
|
export function flatMultiLevelRoutes(routes: RouteRecordRaw[]) {
|
||||||
const routesMirror = cloneDeep(routes)
|
const routesMirror = cloneDeep(routes)
|
||||||
routesMirror.forEach((route) => {
|
routesMirror.forEach((route) => {
|
||||||
// 如果路由是三级及其以上路由,对其进行降级处理
|
// 如果路由是三级及其以上路由,对其进行降级处理
|
||||||
@ -25,17 +25,17 @@ export const flatMultiLevelRoutes = (routes: RouteRecordRaw[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 判断路由层级是否大于 2 */
|
/** 判断路由层级是否大于 2 */
|
||||||
const isMultipleRoute = (route: RouteRecordRaw) => {
|
function isMultipleRoute(route: RouteRecordRaw) {
|
||||||
const children = route.children
|
const children = route.children
|
||||||
if (children?.length) {
|
if (children?.length) {
|
||||||
// 只要有一个子路由的 children 长度大于 0,就说明是三级及其以上路由
|
// 只要有一个子路由的 children 长度大于 0,就说明是三级及其以上路由
|
||||||
return children.some((child) => child.children?.length)
|
return children.some(child => child.children?.length)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 生成二级路由 */
|
/** 生成二级路由 */
|
||||||
const promoteRouteLevel = (route: RouteRecordRaw) => {
|
function promoteRouteLevel(route: RouteRecordRaw) {
|
||||||
// 创建 router 实例是为了获取到当前传入的 route 的所有路由信息
|
// 创建 router 实例是为了获取到当前传入的 route 的所有路由信息
|
||||||
let router: Router | null = createRouter({
|
let router: Router | null = createRouter({
|
||||||
history,
|
history,
|
||||||
@ -46,13 +46,13 @@ const promoteRouteLevel = (route: RouteRecordRaw) => {
|
|||||||
addToChildren(routes, route.children || [], route)
|
addToChildren(routes, route.children || [], route)
|
||||||
router = null
|
router = null
|
||||||
// 转为二级路由后,去除所有子路由中的 children
|
// 转为二级路由后,去除所有子路由中的 children
|
||||||
route.children = route.children?.map((item) => omit(item, "children") as RouteRecordRaw)
|
route.children = route.children?.map(item => omit(item, "children") as RouteRecordRaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将给定的子路由添加到指定的路由模块中 */
|
/** 将给定的子路由添加到指定的路由模块中 */
|
||||||
const addToChildren = (routes: RouteRecordNormalized[], children: RouteRecordRaw[], routeModule: RouteRecordRaw) => {
|
function addToChildren(routes: RouteRecordNormalized[], children: RouteRecordRaw[], routeModule: RouteRecordRaw) {
|
||||||
children.forEach((child) => {
|
children.forEach((child) => {
|
||||||
const route = routes.find((item) => item.name === child.name)
|
const route = routes.find(item => item.name === child.name)
|
||||||
if (route) {
|
if (route) {
|
||||||
// 初始化 routeModule 的 children
|
// 初始化 routeModule 的 children
|
||||||
routeModule.children = routeModule.children || []
|
routeModule.children = routeModule.children || []
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { type RouteRecordRaw, createRouter } from "vue-router"
|
|
||||||
import { history, flatMultiLevelRoutes } from "./helper"
|
|
||||||
import routeSettings from "@/config/route"
|
import routeSettings from "@/config/route"
|
||||||
|
import { createRouter, type RouteRecordRaw } from "vue-router"
|
||||||
|
import { flatMultiLevelRoutes, history } from "./helper"
|
||||||
|
|
||||||
const Layouts = () => import("@/layouts/index.vue")
|
const Layouts = () => import("@/layouts/index.vue")
|
||||||
|
|
||||||
@ -304,7 +304,8 @@ export function resetRouter() {
|
|||||||
router.hasRoute(name) && router.removeRoute(name)
|
router.hasRoute(name) && router.removeRoute(name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch {
|
}
|
||||||
|
catch {
|
||||||
// 强制刷新浏览器也行,只是交互体验不是很好
|
// 强制刷新浏览器也行,只是交互体验不是很好
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { router } from "@/router"
|
|
||||||
import { useUserStoreHook } from "@/store/modules/user"
|
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission"
|
|
||||||
import { ElMessage } from "element-plus"
|
|
||||||
import { setRouteChange } from "@/hooks/useRouteListener"
|
|
||||||
import { useTitle } from "@/hooks/useTitle"
|
|
||||||
import { getToken } from "@/utils/cache/cookies"
|
|
||||||
import routeSettings from "@/config/route"
|
import routeSettings from "@/config/route"
|
||||||
import isWhiteList from "@/config/white-list"
|
import isWhiteList from "@/config/white-list"
|
||||||
|
import { setRouteChange } from "@/hooks/useRouteListener"
|
||||||
|
import { useTitle } from "@/hooks/useTitle"
|
||||||
|
import { router } from "@/router"
|
||||||
|
import { usePermissionStoreHook } from "@/store/modules/permission"
|
||||||
|
import { useUserStoreHook } from "@/store/modules/user"
|
||||||
|
import { getToken } from "@/utils/cache/cookies"
|
||||||
|
import { ElMessage } from "element-plus"
|
||||||
import NProgress from "nprogress"
|
import NProgress from "nprogress"
|
||||||
import "nprogress/nprogress.css"
|
import "nprogress/nprogress.css"
|
||||||
|
|
||||||
@ -41,10 +41,11 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
// 生成可访问的 Routes
|
// 生成可访问的 Routes
|
||||||
routeSettings.dynamic ? permissionStore.setRoutes(roles) : permissionStore.setAllRoutes()
|
routeSettings.dynamic ? permissionStore.setRoutes(roles) : permissionStore.setAllRoutes()
|
||||||
// 将 "有访问权限的动态路由" 添加到 Router 中
|
// 将 "有访问权限的动态路由" 添加到 Router 中
|
||||||
permissionStore.addRoutes.forEach((route) => router.addRoute(route))
|
permissionStore.addRoutes.forEach(route => router.addRoute(route))
|
||||||
// 设置 replace: true, 因此导航将不会留下历史记录
|
// 设置 replace: true, 因此导航将不会留下历史记录
|
||||||
next({ ...to, replace: true })
|
next({ ...to, replace: true })
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
// 过程中发生任何错误,都直接重置 Token,并重定向到登录页面
|
// 过程中发生任何错误,都直接重置 Token,并重定向到登录页面
|
||||||
userStore.resetToken()
|
userStore.resetToken()
|
||||||
ElMessage.error((error as Error).message || "路由守卫过程发生错误")
|
ElMessage.error((error as Error).message || "路由守卫过程发生错误")
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { reactive, ref, watch } from "vue"
|
import { DeviceEnum, SIDEBAR_CLOSED, SIDEBAR_OPENED } from "@/constants/app-key"
|
||||||
import { pinia } from "@/store"
|
import { pinia } from "@/store"
|
||||||
import { defineStore } from "pinia"
|
|
||||||
import { getSidebarStatus, setSidebarStatus } from "@/utils/cache/local-storage"
|
import { getSidebarStatus, setSidebarStatus } from "@/utils/cache/local-storage"
|
||||||
import { DeviceEnum, SIDEBAR_OPENED, SIDEBAR_CLOSED } from "@/constants/app-key"
|
import { defineStore } from "pinia"
|
||||||
|
import { reactive, ref, watch } from "vue"
|
||||||
|
|
||||||
interface Sidebar {
|
interface Sidebar {
|
||||||
opened: boolean
|
opened: boolean
|
||||||
@ -26,7 +26,7 @@ export const useAppStore = defineStore("app", () => {
|
|||||||
/** 监听侧边栏 opened 状态 */
|
/** 监听侧边栏 opened 状态 */
|
||||||
watch(
|
watch(
|
||||||
() => sidebar.opened,
|
() => sidebar.opened,
|
||||||
(opened) => handleSidebarStatus(opened)
|
opened => handleSidebarStatus(opened)
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 切换侧边栏 */
|
/** 切换侧边栏 */
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { ref } from "vue"
|
import type { RouteRecordRaw } from "vue-router"
|
||||||
import { pinia } from "@/store"
|
import routeSettings from "@/config/route"
|
||||||
import { defineStore } from "pinia"
|
|
||||||
import { type RouteRecordRaw } from "vue-router"
|
|
||||||
import { constantRoutes, dynamicRoutes } from "@/router"
|
import { constantRoutes, dynamicRoutes } from "@/router"
|
||||||
import { flatMultiLevelRoutes } from "@/router/helper"
|
import { flatMultiLevelRoutes } from "@/router/helper"
|
||||||
import routeSettings from "@/config/route"
|
import { pinia } from "@/store"
|
||||||
|
import { defineStore } from "pinia"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
|
function hasPermission(roles: string[], route: RouteRecordRaw) {
|
||||||
const routeRoles = route.meta?.roles
|
const routeRoles = route.meta?.roles
|
||||||
return routeRoles ? roles.some((role) => routeRoles.includes(role)) : true
|
return routeRoles ? roles.some(role => routeRoles.includes(role)) : true
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterDynamicRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
function filterDynamicRoutes(routes: RouteRecordRaw[], roles: string[]) {
|
||||||
const res: RouteRecordRaw[] = []
|
const res: RouteRecordRaw[] = []
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
const tempRoute = { ...route }
|
const tempRoute = { ...route }
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { type Ref, ref, watch } from "vue"
|
import type { LayoutSettings } from "@/config/layouts"
|
||||||
|
import type { Ref } from "vue"
|
||||||
|
import { layoutSettings } from "@/config/layouts"
|
||||||
import { pinia } from "@/store"
|
import { pinia } from "@/store"
|
||||||
import { defineStore } from "pinia"
|
|
||||||
import { type LayoutSettings, layoutSettings } from "@/config/layouts"
|
|
||||||
import { setConfigLayout } from "@/utils/cache/local-storage"
|
import { setConfigLayout } from "@/utils/cache/local-storage"
|
||||||
|
import { defineStore } from "pinia"
|
||||||
|
import { ref, watch } from "vue"
|
||||||
|
|
||||||
type SettingsStore = {
|
type SettingsStore = {
|
||||||
// 使用映射类型来遍历 layoutSettings 对象的键
|
// 使用映射类型来遍历 layoutSettings 对象的键
|
||||||
@ -18,7 +20,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
|||||||
for (const [key, value] of Object.entries(layoutSettings)) {
|
for (const [key, value] of Object.entries(layoutSettings)) {
|
||||||
// 使用类型断言来指定 key 的类型,将 value 包装在 ref 函数中,创建一个响应式变量
|
// 使用类型断言来指定 key 的类型,将 value 包装在 ref 函数中,创建一个响应式变量
|
||||||
const refValue = ref(value)
|
const refValue = ref(value)
|
||||||
// @ts-ignore
|
// @ts-expect-error ignore
|
||||||
state[key as SettingsStoreKey] = refValue
|
state[key as SettingsStoreKey] = refValue
|
||||||
// 监听每个响应式变量
|
// 监听每个响应式变量
|
||||||
watch(refValue, () => {
|
watch(refValue, () => {
|
||||||
@ -31,7 +33,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
|||||||
const _getCacheData = () => {
|
const _getCacheData = () => {
|
||||||
const settings = {} as LayoutSettings
|
const settings = {} as LayoutSettings
|
||||||
for (const [key, value] of Object.entries(state)) {
|
for (const [key, value] of Object.entries(state)) {
|
||||||
// @ts-ignore
|
// @ts-expect-error ignore
|
||||||
settings[key as SettingsStoreKey] = value.value
|
settings[key as SettingsStoreKey] = value.value
|
||||||
}
|
}
|
||||||
return settings
|
return settings
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { ref, watchEffect } from "vue"
|
import type { RouteLocationNormalized } from "vue-router"
|
||||||
import { pinia } from "@/store"
|
import { pinia } from "@/store"
|
||||||
|
import { getCachedViews, getVisitedViews, setCachedViews, setVisitedViews } from "@/utils/cache/local-storage"
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
|
import { ref, watchEffect } from "vue"
|
||||||
import { useSettingsStore } from "./settings"
|
import { useSettingsStore } from "./settings"
|
||||||
import { type RouteLocationNormalized } from "vue-router"
|
|
||||||
import { getVisitedViews, setVisitedViews, getCachedViews, setCachedViews } from "@/utils/cache/local-storage"
|
|
||||||
|
|
||||||
export type TagView = Partial<RouteLocationNormalized>
|
export type TagView = Partial<RouteLocationNormalized>
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ export const useTagsViewStore = defineStore("tags-view", () => {
|
|||||||
// #region add
|
// #region add
|
||||||
const addVisitedView = (view: TagView) => {
|
const addVisitedView = (view: TagView) => {
|
||||||
// 检查是否已经存在相同的 visitedView
|
// 检查是否已经存在相同的 visitedView
|
||||||
const index = visitedViews.value.findIndex((v) => v.path === view.path)
|
const index = visitedViews.value.findIndex(v => v.path === view.path)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
// 防止 query 参数丢失
|
// 防止 query 参数丢失
|
||||||
visitedViews.value[index].fullPath !== view.fullPath && (visitedViews.value[index] = { ...view })
|
visitedViews.value[index].fullPath !== view.fullPath && (visitedViews.value[index] = { ...view })
|
||||||
@ -34,20 +34,23 @@ export const useTagsViewStore = defineStore("tags-view", () => {
|
|||||||
const addCachedView = (view: TagView) => {
|
const addCachedView = (view: TagView) => {
|
||||||
if (typeof view.name !== "string") return
|
if (typeof view.name !== "string") return
|
||||||
if (cachedViews.value.includes(view.name)) return
|
if (cachedViews.value.includes(view.name)) return
|
||||||
if (view.meta?.keepAlive) cachedViews.value.push(view.name)
|
if (view.meta?.keepAlive)
|
||||||
|
cachedViews.value.push(view.name)
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region del
|
// #region del
|
||||||
const delVisitedView = (view: TagView) => {
|
const delVisitedView = (view: TagView) => {
|
||||||
const index = visitedViews.value.findIndex((v) => v.path === view.path)
|
const index = visitedViews.value.findIndex(v => v.path === view.path)
|
||||||
if (index !== -1) visitedViews.value.splice(index, 1)
|
if (index !== -1)
|
||||||
|
visitedViews.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const delCachedView = (view: TagView) => {
|
const delCachedView = (view: TagView) => {
|
||||||
if (typeof view.name !== "string") return
|
if (typeof view.name !== "string") return
|
||||||
const index = cachedViews.value.indexOf(view.name)
|
const index = cachedViews.value.indexOf(view.name)
|
||||||
if (index !== -1) cachedViews.value.splice(index, 1)
|
if (index !== -1)
|
||||||
|
cachedViews.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
@ -73,7 +76,7 @@ export const useTagsViewStore = defineStore("tags-view", () => {
|
|||||||
// #region delAll
|
// #region delAll
|
||||||
const delAllVisitedViews = () => {
|
const delAllVisitedViews = () => {
|
||||||
// 保留固定的 tags
|
// 保留固定的 tags
|
||||||
visitedViews.value = visitedViews.value.filter((tag) => tag.meta?.affix)
|
visitedViews.value = visitedViews.value.filter(tag => tag.meta?.affix)
|
||||||
}
|
}
|
||||||
|
|
||||||
const delAllCachedViews = () => {
|
const delAllCachedViews = () => {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { ref } from "vue"
|
import type { LoginRequestData } from "@/api/login/types/login"
|
||||||
import { pinia } from "@/store"
|
import { getUserInfoApi, loginApi } from "@/api/login"
|
||||||
import { defineStore } from "pinia"
|
|
||||||
import { useTagsViewStore } from "./tags-view"
|
|
||||||
import { useSettingsStore } from "./settings"
|
|
||||||
import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
|
|
||||||
import { resetRouter } from "@/router"
|
|
||||||
import { loginApi, getUserInfoApi } from "@/api/login"
|
|
||||||
import { type LoginRequestData } from "@/api/login/types/login"
|
|
||||||
import routeSettings from "@/config/route"
|
import routeSettings from "@/config/route"
|
||||||
|
import { resetRouter } from "@/router"
|
||||||
|
import { pinia } from "@/store"
|
||||||
|
import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
|
||||||
|
import { defineStore } from "pinia"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { useSettingsStore } from "./settings"
|
||||||
|
import { useTagsViewStore } from "./tags-view"
|
||||||
|
|
||||||
export const useUserStore = defineStore("user", () => {
|
export const useUserStore = defineStore("user", () => {
|
||||||
const token = ref<string>(getToken() || "")
|
const token = ref<string>(getToken() || "")
|
||||||
@ -32,7 +32,7 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
}
|
}
|
||||||
/** 模拟角色变化 */
|
/** 模拟角色变化 */
|
||||||
const changeRoles = async (role: string) => {
|
const changeRoles = async (role: string) => {
|
||||||
const newToken = "token-" + role
|
const newToken = `token-${role}`
|
||||||
token.value = newToken
|
token.value = newToken
|
||||||
setToken(newToken)
|
setToken(newToken)
|
||||||
// 用刷新页面代替重新登录
|
// 用刷新页面代替重新登录
|
||||||
|
6
src/utils/cache/cookies.ts
vendored
6
src/utils/cache/cookies.ts
vendored
@ -3,12 +3,12 @@
|
|||||||
import CacheKey from "@/constants/cache-key"
|
import CacheKey from "@/constants/cache-key"
|
||||||
import Cookies from "js-cookie"
|
import Cookies from "js-cookie"
|
||||||
|
|
||||||
export const getToken = () => {
|
export function getToken() {
|
||||||
return Cookies.get(CacheKey.TOKEN)
|
return Cookies.get(CacheKey.TOKEN)
|
||||||
}
|
}
|
||||||
export const setToken = (token: string) => {
|
export function setToken(token: string) {
|
||||||
Cookies.set(CacheKey.TOKEN, token)
|
Cookies.set(CacheKey.TOKEN, token)
|
||||||
}
|
}
|
||||||
export const removeToken = () => {
|
export function removeToken() {
|
||||||
Cookies.remove(CacheKey.TOKEN)
|
Cookies.remove(CacheKey.TOKEN)
|
||||||
}
|
}
|
||||||
|
30
src/utils/cache/local-storage.ts
vendored
30
src/utils/cache/local-storage.ts
vendored
@ -1,48 +1,48 @@
|
|||||||
/** 统一处理 localStorage */
|
/** 统一处理 localStorage */
|
||||||
|
|
||||||
|
import type { LayoutSettings } from "@/config/layouts"
|
||||||
|
import type { SidebarClosed, SidebarOpened } from "@/constants/app-key"
|
||||||
|
import type { ThemeName } from "@/hooks/useTheme"
|
||||||
|
import type { TagView } from "@/store/modules/tags-view"
|
||||||
import CacheKey from "@/constants/cache-key"
|
import CacheKey from "@/constants/cache-key"
|
||||||
import { type SidebarOpened, type SidebarClosed } from "@/constants/app-key"
|
|
||||||
import { type ThemeName } from "@/hooks/useTheme"
|
|
||||||
import { type TagView } from "@/store/modules/tags-view"
|
|
||||||
import { type LayoutSettings } from "@/config/layouts"
|
|
||||||
|
|
||||||
// #region 系统布局配置
|
// #region 系统布局配置
|
||||||
export const getConfigLayout = () => {
|
export function getConfigLayout() {
|
||||||
const json = localStorage.getItem(CacheKey.CONFIG_LAYOUT)
|
const json = localStorage.getItem(CacheKey.CONFIG_LAYOUT)
|
||||||
return json ? (JSON.parse(json) as LayoutSettings) : null
|
return json ? (JSON.parse(json) as LayoutSettings) : null
|
||||||
}
|
}
|
||||||
export const setConfigLayout = (settings: LayoutSettings) => {
|
export function setConfigLayout(settings: LayoutSettings) {
|
||||||
localStorage.setItem(CacheKey.CONFIG_LAYOUT, JSON.stringify(settings))
|
localStorage.setItem(CacheKey.CONFIG_LAYOUT, JSON.stringify(settings))
|
||||||
}
|
}
|
||||||
export const removeConfigLayout = () => {
|
export function removeConfigLayout() {
|
||||||
localStorage.removeItem(CacheKey.CONFIG_LAYOUT)
|
localStorage.removeItem(CacheKey.CONFIG_LAYOUT)
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region 侧边栏状态
|
// #region 侧边栏状态
|
||||||
export const getSidebarStatus = () => {
|
export function getSidebarStatus() {
|
||||||
return localStorage.getItem(CacheKey.SIDEBAR_STATUS)
|
return localStorage.getItem(CacheKey.SIDEBAR_STATUS)
|
||||||
}
|
}
|
||||||
export const setSidebarStatus = (sidebarStatus: SidebarOpened | SidebarClosed) => {
|
export function setSidebarStatus(sidebarStatus: SidebarOpened | SidebarClosed) {
|
||||||
localStorage.setItem(CacheKey.SIDEBAR_STATUS, sidebarStatus)
|
localStorage.setItem(CacheKey.SIDEBAR_STATUS, sidebarStatus)
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region 正在应用的主题名称
|
// #region 正在应用的主题名称
|
||||||
export const getActiveThemeName = () => {
|
export function getActiveThemeName() {
|
||||||
return localStorage.getItem(CacheKey.ACTIVE_THEME_NAME) as ThemeName | null
|
return localStorage.getItem(CacheKey.ACTIVE_THEME_NAME) as ThemeName | null
|
||||||
}
|
}
|
||||||
export const setActiveThemeName = (themeName: ThemeName) => {
|
export function setActiveThemeName(themeName: ThemeName) {
|
||||||
localStorage.setItem(CacheKey.ACTIVE_THEME_NAME, themeName)
|
localStorage.setItem(CacheKey.ACTIVE_THEME_NAME, themeName)
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region 标签栏
|
// #region 标签栏
|
||||||
export const getVisitedViews = () => {
|
export function getVisitedViews() {
|
||||||
const json = localStorage.getItem(CacheKey.VISITED_VIEWS)
|
const json = localStorage.getItem(CacheKey.VISITED_VIEWS)
|
||||||
return JSON.parse(json ?? "[]") as TagView[]
|
return JSON.parse(json ?? "[]") as TagView[]
|
||||||
}
|
}
|
||||||
export const setVisitedViews = (views: TagView[]) => {
|
export function setVisitedViews(views: TagView[]) {
|
||||||
views.forEach((view) => {
|
views.forEach((view) => {
|
||||||
// 删除不必要的属性,防止 JSON.stringify 处理到循环引用
|
// 删除不必要的属性,防止 JSON.stringify 处理到循环引用
|
||||||
delete view.matched
|
delete view.matched
|
||||||
@ -50,11 +50,11 @@ export const setVisitedViews = (views: TagView[]) => {
|
|||||||
})
|
})
|
||||||
localStorage.setItem(CacheKey.VISITED_VIEWS, JSON.stringify(views))
|
localStorage.setItem(CacheKey.VISITED_VIEWS, JSON.stringify(views))
|
||||||
}
|
}
|
||||||
export const getCachedViews = () => {
|
export function getCachedViews() {
|
||||||
const json = localStorage.getItem(CacheKey.CACHED_VIEWS)
|
const json = localStorage.getItem(CacheKey.CACHED_VIEWS)
|
||||||
return JSON.parse(json ?? "[]") as string[]
|
return JSON.parse(json ?? "[]") as string[]
|
||||||
}
|
}
|
||||||
export const setCachedViews = (views: string[]) => {
|
export function setCachedViews(views: string[]) {
|
||||||
localStorage.setItem(CacheKey.CACHED_VIEWS, JSON.stringify(views))
|
localStorage.setItem(CacheKey.CACHED_VIEWS, JSON.stringify(views))
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/** 获取指定元素(默认全局)上的 CSS 变量的值 */
|
/** 获取指定元素(默认全局)上的 CSS 变量的值 */
|
||||||
export const getCssVar = (varName: string, element: HTMLElement = document.documentElement) => {
|
export function getCssVar(varName: string, element: HTMLElement = document.documentElement) {
|
||||||
if (!varName?.startsWith("--")) {
|
if (!varName?.startsWith("--")) {
|
||||||
console.warn("CSS 变量名应以 '--' 开头")
|
console.warn("CSS 变量名应以 '--' 开头")
|
||||||
return ""
|
return ""
|
||||||
@ -9,7 +9,7 @@ export const getCssVar = (varName: string, element: HTMLElement = document.docum
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 设置指定元素(默认全局)上的 CSS 变量的值 */
|
/** 设置指定元素(默认全局)上的 CSS 变量的值 */
|
||||||
export const setCssVar = (varName: string, value: string, element: HTMLElement = document.documentElement) => {
|
export function setCssVar(varName: string, value: string, element: HTMLElement = document.documentElement) {
|
||||||
if (!varName?.startsWith("--")) {
|
if (!varName?.startsWith("--")) {
|
||||||
console.warn("CSS 变量名应以 '--' 开头")
|
console.warn("CSS 变量名应以 '--' 开头")
|
||||||
return
|
return
|
||||||
|
@ -3,7 +3,7 @@ import dayjs from "dayjs"
|
|||||||
const INVALID_DATE = "N/A"
|
const INVALID_DATE = "N/A"
|
||||||
|
|
||||||
/** 格式化日期时间 */
|
/** 格式化日期时间 */
|
||||||
export const formatDateTime = (datetime: string | number | Date = "", template: string = "YYYY-MM-DD HH:mm:ss") => {
|
export function formatDateTime(datetime: string | number | Date = "", template: string = "YYYY-MM-DD HH:mm:ss") {
|
||||||
const day = dayjs(datetime)
|
const day = dayjs(datetime)
|
||||||
return day.isValid() ? day.format(template) : INVALID_DATE
|
return day.isValid() ? day.format(template) : INVALID_DATE
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useUserStore } from "@/store/modules/user"
|
import { useUserStore } from "@/store/modules/user"
|
||||||
|
|
||||||
/** 全局权限判断函数,和权限指令 v-permission 功能类似 */
|
/** 全局权限判断函数,和权限指令 v-permission 功能类似 */
|
||||||
export const checkPermission = (permissionRoles: string[]): boolean => {
|
export function checkPermission(permissionRoles: string[]): boolean {
|
||||||
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
|
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
|
||||||
const { roles } = useUserStore()
|
const { roles } = useUserStore()
|
||||||
return roles.some((role) => permissionRoles.includes(role))
|
return roles.some(role => permissionRoles.includes(role))
|
||||||
} else {
|
} else {
|
||||||
console.error("need roles! Like checkPermission(['admin','editor'])")
|
console.error("need roles! Like checkPermission(['admin','editor'])")
|
||||||
return false
|
return false
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import axios, { type AxiosInstance, type AxiosRequestConfig } from "axios"
|
|
||||||
import { useUserStore } from "@/store/modules/user"
|
import { useUserStore } from "@/store/modules/user"
|
||||||
|
import axios, { type AxiosInstance, type AxiosRequestConfig } from "axios"
|
||||||
import { ElMessage } from "element-plus"
|
import { ElMessage } from "element-plus"
|
||||||
import { get, merge } from "lodash-es"
|
import { get, merge } from "lodash-es"
|
||||||
import { getToken } from "./cache/cookies"
|
import { getToken } from "./cache/cookies"
|
||||||
@ -16,9 +16,9 @@ function createService() {
|
|||||||
const service = axios.create()
|
const service = axios.create()
|
||||||
// 请求拦截
|
// 请求拦截
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config) => config,
|
config => config,
|
||||||
// 发送失败
|
// 发送失败
|
||||||
(error) => Promise.reject(error)
|
error => Promise.reject(error)
|
||||||
)
|
)
|
||||||
// 响应拦截(可根据具体业务作出相应的调整)
|
// 响应拦截(可根据具体业务作出相应的调整)
|
||||||
service.interceptors.response.use(
|
service.interceptors.response.use(
|
||||||
@ -27,7 +27,8 @@ function createService() {
|
|||||||
const apiData = response.data
|
const apiData = response.data
|
||||||
// 二进制数据则直接返回
|
// 二进制数据则直接返回
|
||||||
const responseType = response.request?.responseType
|
const responseType = response.request?.responseType
|
||||||
if (responseType === "blob" || responseType === "arraybuffer") return apiData
|
if (responseType === "blob" || responseType === "arraybuffer")
|
||||||
|
return apiData
|
||||||
// 这个 code 是和后端约定的业务 code
|
// 这个 code 是和后端约定的业务 code
|
||||||
const code = apiData.code
|
const code = apiData.code
|
||||||
// 如果没有 code, 代表这不是项目后端开发的 api
|
// 如果没有 code, 代表这不是项目后端开发的 api
|
||||||
@ -103,7 +104,7 @@ function createRequest(service: AxiosInstance) {
|
|||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
headers: {
|
headers: {
|
||||||
// 携带 Token
|
// 携带 Token
|
||||||
Authorization: token ? `Bearer ${token}` : undefined,
|
"Authorization": token ? `Bearer ${token}` : undefined,
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
|
@ -1,84 +1,84 @@
|
|||||||
/** 判断是否为数组 */
|
/** 判断是否为数组 */
|
||||||
export const isArray = <T>(arg: T) => {
|
export function isArray<T>(arg: T) {
|
||||||
return Array.isArray ? Array.isArray(arg) : Object.prototype.toString.call(arg) === "[object Array]"
|
return Array.isArray ? Array.isArray(arg) : Object.prototype.toString.call(arg) === "[object Array]"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为字符串 */
|
/** 判断是否为字符串 */
|
||||||
export const isString = <T>(str: T) => {
|
export function isString<T>(str: T) {
|
||||||
return typeof str === "string" || str instanceof String
|
return typeof str === "string" || str instanceof String
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为外链 */
|
/** 判断是否为外链 */
|
||||||
export const isExternal = (path: string) => {
|
export function isExternal(path: string) {
|
||||||
const reg = /^(https?:|mailto:|tel:)/
|
const reg = /^(https?:|mailto:|tel:)/
|
||||||
return reg.test(path)
|
return reg.test(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为网址(带协议) */
|
/** 判断是否为网址(带协议) */
|
||||||
export const isUrl = (url: string) => {
|
export function isUrl(url: string) {
|
||||||
const reg = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/
|
const reg = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{1,64})?\.)+[a-z]{2,6}\/?/
|
||||||
return reg.test(url)
|
return reg.test(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为网址或 IP(带端口) */
|
/** 判断是否为网址或 IP(带端口) */
|
||||||
export const isUrlPort = (url: string) => {
|
export function isUrlPort(url: string) {
|
||||||
const reg = /^((ht|f)tps?:\/\/)?[\w-]+(\.[\w-]+)+:\d{1,5}\/?$/
|
const reg = /^((ht|f)tps?:\/\/)?[\w-]+(\.[\w-]+)+:\d{1,5}\/?$/
|
||||||
return reg.test(url)
|
return reg.test(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为域名(不带协议) */
|
/** 判断是否为域名(不带协议) */
|
||||||
export const isDomain = (domain: string) => {
|
export function isDomain(domain: string) {
|
||||||
const reg = /^([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})$/
|
const reg = /^([0-9a-z-]+\.)+([a-z]{2,})$/i
|
||||||
return reg.test(domain)
|
return reg.test(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断版本号格式是否为 X.Y.Z */
|
/** 判断版本号格式是否为 X.Y.Z */
|
||||||
export const isVersion = (version: string) => {
|
export function isVersion(version: string) {
|
||||||
const reg = /^\d+(?:\.\d+){2}$/
|
const reg = /^\d+(?:\.\d+){2}$/
|
||||||
return reg.test(version)
|
return reg.test(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断时间格式是否为 24 小时制(HH:mm:ss) */
|
/** 判断时间格式是否为 24 小时制(HH:mm:ss) */
|
||||||
export const is24H = (time: string) => {
|
export function is24H(time: string) {
|
||||||
const reg = /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
|
const reg = /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
|
||||||
return reg.test(time)
|
return reg.test(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为手机号(1 开头) */
|
/** 判断是否为手机号(1 开头) */
|
||||||
export const isPhoneNumber = (str: string) => {
|
export function isPhoneNumber(str: string) {
|
||||||
const reg = /^(?:(?:\+|00)86)?1\d{10}$/
|
const reg = /^(?:(?:\+|00)86)?1\d{10}$/
|
||||||
return reg.test(str)
|
return reg.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为第二代身份证(18 位) */
|
/** 判断是否为第二代身份证(18 位) */
|
||||||
export const isChineseIdCard = (str: string) => {
|
export function isChineseIdCard(str: string) {
|
||||||
const reg = /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/
|
const reg = /^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[12]\d|30|31)\d{3}[\dX]$/i
|
||||||
return reg.test(str)
|
return reg.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为 Email(支持中文邮箱) */
|
/** 判断是否为 Email(支持中文邮箱) */
|
||||||
export const isEmail = (email: string) => {
|
export function isEmail(email: string) {
|
||||||
const reg = /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
|
const reg = /^[A-Z0-9\u4E00-\u9FA5]+@[\w-]+(\.[\w-]+)+$/i
|
||||||
return reg.test(email)
|
return reg.test(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为 MAC 地址 */
|
/** 判断是否为 MAC 地址 */
|
||||||
export const isMAC = (mac: string) => {
|
export function isMAC(mac: string) {
|
||||||
const reg =
|
const reg
|
||||||
/^(([a-f0-9][0,2,4,6,8,a,c,e]:([a-f0-9]{2}:){4})|([a-f0-9][0,2,4,6,8,a,c,e]-([a-f0-9]{2}-){4}))[a-f0-9]{2}$/i
|
= /^(([a-f0-9][0,2468ace]:([a-f0-9]{2}:){4})|([a-f0-9][0,2468ace]-([a-f0-9]{2}-){4}))[a-f0-9]{2}$/i
|
||||||
return reg.test(mac)
|
return reg.test(mac)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为 IPv4 地址 */
|
/** 判断是否为 IPv4 地址 */
|
||||||
export const isIPv4 = (ip: string) => {
|
export function isIPv4(ip: string) {
|
||||||
const reg =
|
const reg
|
||||||
/^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(?::(?:[0-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?$/
|
= /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(?::(?:\d|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5]))?$/
|
||||||
return reg.test(ip)
|
return reg.test(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为车牌(兼容新能源车牌) */
|
/** 判断是否为车牌(兼容新能源车牌) */
|
||||||
export const isLicensePlate = (str: string) => {
|
export function isLicensePlate(str: string) {
|
||||||
const reg =
|
const reg
|
||||||
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/
|
= /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/
|
||||||
return reg.test(str)
|
return reg.test(str)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ErrorPageLayout from "./components/ErrorPageLayout.vue"
|
|
||||||
import Svg403 from "@/assets/error-page/403.svg?component" // vite-svg-loader 插件的功能
|
import Svg403 from "@/assets/error-page/403.svg?component" // vite-svg-loader 插件的功能
|
||||||
|
import ErrorPageLayout from "./components/ErrorPageLayout.vue"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ErrorPageLayout from "./components/ErrorPageLayout.vue"
|
|
||||||
import Svg404 from "@/assets/error-page/404.svg?component" // vite-svg-loader 插件的功能
|
import Svg404 from "@/assets/error-page/404.svg?component" // vite-svg-loader 插件的功能
|
||||||
|
import ErrorPageLayout from "./components/ErrorPageLayout.vue"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<el-button type="primary">回到首页</el-button>
|
<el-button type="primary">
|
||||||
|
回到首页
|
||||||
|
</el-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useFetchSelect } from "@/hooks/useFetchSelect"
|
|
||||||
import { getSelectDataApi } from "@/api/hook-demo/use-fetch-select"
|
import { getSelectDataApi } from "@/api/hook-demo/use-fetch-select"
|
||||||
|
import { useFetchSelect } from "@/hooks/useFetchSelect"
|
||||||
|
|
||||||
const { loading, options, value } = useFetchSelect({
|
const { loading, options, value } = useFetchSelect({
|
||||||
api: getSelectDataApi
|
api: getSelectDataApi
|
||||||
@ -11,10 +11,10 @@ const { loading, options, value } = useFetchSelect({
|
|||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<h4>该示例是演示:通过 hook 自动调用 api 后拿到 Select 组件需要的数据并传递给 Select 组件</h4>
|
<h4>该示例是演示:通过 hook 自动调用 api 后拿到 Select 组件需要的数据并传递给 Select 组件</h4>
|
||||||
<h5>Select 示例</h5>
|
<h5>Select 示例</h5>
|
||||||
<el-select :loading="loading" v-model="value" filterable>
|
<el-select v-model="value" :loading="loading" filterable>
|
||||||
<el-option v-for="(item, index) in options" v-bind="item" :key="index" placeholder="请选择" />
|
<el-option v-for="(item, index) in options" v-bind="item" :key="index" placeholder="请选择" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<h5>Select V2 示例(如果数据量过多,可以选择该组件)</h5>
|
<h5>Select V2 示例(如果数据量过多,可以选择该组件)</h5>
|
||||||
<el-select-v2 :loading="loading" v-model="value" :options="options" filterable placeholder="请选择" />
|
<el-select-v2 v-model="value" :loading="loading" :options="options" filterable placeholder="请选择" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { getErrorApi, getSuccessApi } from "@/api/hook-demo/use-fullscreen-loading"
|
||||||
import { useFullscreenLoading } from "@/hooks/useFullscreenLoading"
|
import { useFullscreenLoading } from "@/hooks/useFullscreenLoading"
|
||||||
import { getSuccessApi, getErrorApi } from "@/api/hook-demo/use-fullscreen-loading"
|
|
||||||
import { ElMessage } from "element-plus"
|
import { ElMessage } from "element-plus"
|
||||||
|
|
||||||
const svg = `
|
const svg = `
|
||||||
@ -21,7 +21,7 @@ const options = {
|
|||||||
svgViewBox: "-10, -10, 50, 50"
|
svgViewBox: "-10, -10, 50, 50"
|
||||||
}
|
}
|
||||||
|
|
||||||
const querySuccess = async () => {
|
async function querySuccess() {
|
||||||
// 注意:
|
// 注意:
|
||||||
// 1. getSuccessApi 是一个函数而非函数调用
|
// 1. getSuccessApi 是一个函数而非函数调用
|
||||||
// 2. 如需给 getSuccessApi 函数传递参数,请在后面的括号中进行(真正的 getSuccessApi 调用)
|
// 2. 如需给 getSuccessApi 函数传递参数,请在后面的括号中进行(真正的 getSuccessApi 调用)
|
||||||
@ -29,10 +29,11 @@ const querySuccess = async () => {
|
|||||||
ElMessage.success(`${res.message},传参为 ${res.data.list.toString()}`)
|
ElMessage.success(`${res.message},传参为 ${res.data.list.toString()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryError = async () => {
|
async function queryError() {
|
||||||
try {
|
try {
|
||||||
await useFullscreenLoading(getErrorApi, options)()
|
await useFullscreenLoading(getErrorApi, options)()
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
ElMessage.error((error as Error).message)
|
ElMessage.error((error as Error).message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,7 +42,11 @@ const queryError = async () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<h4>该示例是演示:通过将要执行的函数传递给 hook,让 hook 自动开启全屏 loading,函数执行结束后自动关闭 loading</h4>
|
<h4>该示例是演示:通过将要执行的函数传递给 hook,让 hook 自动开启全屏 loading,函数执行结束后自动关闭 loading</h4>
|
||||||
<el-button type="primary" @click="querySuccess">查询成功</el-button>
|
<el-button type="primary" @click="querySuccess">
|
||||||
<el-button type="danger" @click="queryError">查询失败</el-button>
|
查询成功
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" @click="queryError">
|
||||||
|
查询失败
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue"
|
|
||||||
import { useWatermark } from "@/hooks/useWatermark"
|
import { useWatermark } from "@/hooks/useWatermark"
|
||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
const localRef = ref<HTMLElement | null>(null)
|
const localRef = ref<HTMLElement | null>(null)
|
||||||
const { setWatermark, clearWatermark } = useWatermark(localRef)
|
const { setWatermark, clearWatermark } = useWatermark(localRef)
|
||||||
@ -15,21 +15,29 @@ const { setWatermark: setGlobalWatermark, clearWatermark: clearGlobalWatermark }
|
|||||||
</h4>
|
</h4>
|
||||||
<div ref="localRef" class="local" />
|
<div ref="localRef" class="local" />
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button type="primary" @click="setWatermark('局部水印', { color: '#409eff' })">创建局部水印</el-button>
|
<el-button type="primary" @click="setWatermark('局部水印', { color: '#409eff' })">
|
||||||
|
创建局部水印
|
||||||
|
</el-button>
|
||||||
<el-button type="warning" @click="setWatermark('没有防御功能的局部水印', { color: '#e6a23c', defense: false })">
|
<el-button type="warning" @click="setWatermark('没有防御功能的局部水印', { color: '#e6a23c', defense: false })">
|
||||||
关闭防御功能
|
关闭防御功能
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" @click="clearWatermark">清除局部水印</el-button>
|
<el-button type="danger" @click="clearWatermark">
|
||||||
|
清除局部水印
|
||||||
|
</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button type="primary" @click="setGlobalWatermark('全局水印', { color: '#409eff' })">创建全局水印</el-button>
|
<el-button type="primary" @click="setGlobalWatermark('全局水印', { color: '#409eff' })">
|
||||||
|
创建全局水印
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
@click="setGlobalWatermark('没有防御功能的全局水印', { color: '#e6a23c', defense: false })"
|
@click="setGlobalWatermark('没有防御功能的全局水印', { color: '#e6a23c', defense: false })"
|
||||||
>
|
>
|
||||||
关闭防御功能
|
关闭防御功能
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" @click="clearGlobalWatermark">清除全局水印</el-button>
|
<el-button type="danger" @click="clearGlobalWatermark">
|
||||||
|
清除全局水印
|
||||||
|
</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { LoginRequestData } from "@/api/login/types/login"
|
||||||
|
import type { FormInstance, FormRules } from "element-plus"
|
||||||
|
import { getLoginCodeApi } from "@/api/login"
|
||||||
|
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
||||||
|
import { useUserStore } from "@/store/modules/user"
|
||||||
|
import { Key, Loading, Lock, Picture, User } from "@element-plus/icons-vue"
|
||||||
import { reactive, ref } from "vue"
|
import { reactive, ref } from "vue"
|
||||||
import { useRouter } from "vue-router"
|
import { useRouter } from "vue-router"
|
||||||
import { useUserStore } from "@/store/modules/user"
|
|
||||||
import { type FormInstance, type FormRules } from "element-plus"
|
|
||||||
import { User, Lock, Key, Picture, Loading } from "@element-plus/icons-vue"
|
|
||||||
import { getLoginCodeApi } from "@/api/login"
|
|
||||||
import { type LoginRequestData } from "@/api/login/types/login"
|
|
||||||
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
|
||||||
import Owl from "./components/Owl.vue"
|
import Owl from "./components/Owl.vue"
|
||||||
import { useFocus } from "./hooks/useFocus"
|
import { useFocus } from "./hooks/useFocus"
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ const loginFormRules: FormRules = {
|
|||||||
code: [{ required: true, message: "请输入验证码", trigger: "blur" }]
|
code: [{ required: true, message: "请输入验证码", trigger: "blur" }]
|
||||||
}
|
}
|
||||||
/** 登录逻辑 */
|
/** 登录逻辑 */
|
||||||
const handleLogin = () => {
|
function handleLogin() {
|
||||||
loginFormRef.value?.validate((valid: boolean, fields) => {
|
loginFormRef.value?.validate((valid: boolean, fields) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@ -58,7 +58,7 @@ const handleLogin = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/** 创建验证码 */
|
/** 创建验证码 */
|
||||||
const createCode = () => {
|
function createCode() {
|
||||||
// 先清空验证码的输入
|
// 先清空验证码的输入
|
||||||
loginFormData.code = ""
|
loginFormData.code = ""
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
@ -78,7 +78,7 @@ createCode()
|
|||||||
<Owl :close-eyes="isFocus" />
|
<Owl :close-eyes="isFocus" />
|
||||||
<div class="login-card">
|
<div class="login-card">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<img src="@/assets/layouts/logo-text-2.png" />
|
<img src="@/assets/layouts/logo-text-2.png">
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<el-form ref="loginFormRef" :model="loginFormData" :rules="loginFormRules" @keyup.enter="handleLogin">
|
<el-form ref="loginFormRef" :model="loginFormData" :rules="loginFormRules" @keyup.enter="handleLogin">
|
||||||
@ -116,7 +116,7 @@ createCode()
|
|||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-image :src="codeUrl" @click="createCode" draggable="false">
|
<el-image :src="codeUrl" draggable="false" @click="createCode">
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Picture />
|
<Picture />
|
||||||
@ -131,7 +131,9 @@ createCode()
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-button :loading="loading" type="primary" size="large" @click.prevent="handleLogin">登 录</el-button>
|
<el-button :loading="loading" type="primary" size="large" @click.prevent="handleLogin">
|
||||||
|
登 录
|
||||||
|
</el-button>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Menu1-1"
|
name: "Menu11"
|
||||||
})
|
})
|
||||||
|
|
||||||
const text = ref("")
|
const text = ref("")
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Menu1-2-1"
|
name: "Menu121"
|
||||||
})
|
})
|
||||||
|
|
||||||
const text = ref("")
|
const text = ref("")
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Menu1-2-2"
|
name: "Menu122"
|
||||||
})
|
})
|
||||||
|
|
||||||
const text = ref("")
|
const text = ref("")
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Menu1-3"
|
name: "Menu13"
|
||||||
})
|
})
|
||||||
|
|
||||||
const text = ref("")
|
const text = ref("")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from "vue"
|
|
||||||
import { useUserStore } from "@/store/modules/user"
|
import { useUserStore } from "@/store/modules/user"
|
||||||
|
import { ref, watch } from "vue"
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const switchRoles = ref(userStore.roles[0])
|
const switchRoles = ref(userStore.roles[0])
|
||||||
|
@ -5,7 +5,9 @@ import SwitchRoles from "./components/SwitchRoles.vue"
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<SwitchRoles />
|
<SwitchRoles />
|
||||||
<el-tag type="warning" size="large">当前页面只有 admin 角色可见,切换角色后将不能进入该页面</el-tag>
|
<el-tag type="warning" size="large">
|
||||||
|
当前页面只有 admin 角色可见,切换角色后将不能进入该页面
|
||||||
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { useRoute, useRouter } from "vue-router"
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
router.replace({ path: "/" + route.params.path, query: route.query })
|
router.replace({ path: `/${route.params.path}`, query: route.query })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, watch } from "vue"
|
import type { CreateOrUpdateTableRequestData, TableData } from "@/api/table/types/table"
|
||||||
import { createTableDataApi, deleteTableDataApi, updateTableDataApi, getTableDataApi } from "@/api/table"
|
import { createTableDataApi, deleteTableDataApi, getTableDataApi, updateTableDataApi } from "@/api/table"
|
||||||
import { type CreateOrUpdateTableRequestData, type TableData } from "@/api/table/types/table"
|
|
||||||
import { type FormInstance, type FormRules, ElMessage, ElMessageBox } from "element-plus"
|
|
||||||
import { Search, Refresh, CirclePlus, Delete, Download, RefreshRight } from "@element-plus/icons-vue"
|
|
||||||
import { usePagination } from "@/hooks/usePagination"
|
import { usePagination } from "@/hooks/usePagination"
|
||||||
|
import { CirclePlus, Delete, Download, Refresh, RefreshRight, Search } from "@element-plus/icons-vue"
|
||||||
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
|
import { reactive, ref, watch } from "vue"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
// 命名当前组件
|
// 命名当前组件
|
||||||
@ -28,7 +28,7 @@ const formRules: FormRules<CreateOrUpdateTableRequestData> = {
|
|||||||
username: [{ required: true, trigger: "blur", message: "请输入用户名" }],
|
username: [{ required: true, trigger: "blur", message: "请输入用户名" }],
|
||||||
password: [{ required: true, trigger: "blur", message: "请输入密码" }]
|
password: [{ required: true, trigger: "blur", message: "请输入密码" }]
|
||||||
}
|
}
|
||||||
const handleCreateOrUpdate = () => {
|
function handleCreateOrUpdate() {
|
||||||
formRef.value?.validate((valid: boolean, fields) => {
|
formRef.value?.validate((valid: boolean, fields) => {
|
||||||
if (!valid) return console.error("表单校验不通过", fields)
|
if (!valid) return console.error("表单校验不通过", fields)
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@ -44,14 +44,14 @@ const handleCreateOrUpdate = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const resetForm = () => {
|
function resetForm() {
|
||||||
formRef.value?.clearValidate()
|
formRef.value?.clearValidate()
|
||||||
formData.value = cloneDeep(DEFAULT_FORM_DATA)
|
formData.value = cloneDeep(DEFAULT_FORM_DATA)
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region 删
|
// #region 删
|
||||||
const handleDelete = (row: TableData) => {
|
function handleDelete(row: TableData) {
|
||||||
ElMessageBox.confirm(`正在删除用户:${row.username},确认删除?`, "提示", {
|
ElMessageBox.confirm(`正在删除用户:${row.username},确认删除?`, "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
@ -66,7 +66,7 @@ const handleDelete = (row: TableData) => {
|
|||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region 改
|
// #region 改
|
||||||
const handleUpdate = (row: TableData) => {
|
function handleUpdate(row: TableData) {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
formData.value = cloneDeep(row)
|
formData.value = cloneDeep(row)
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ const searchData = reactive({
|
|||||||
username: "",
|
username: "",
|
||||||
phone: ""
|
phone: ""
|
||||||
})
|
})
|
||||||
const getTableData = () => {
|
function getTableData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
getTableDataApi({
|
getTableDataApi({
|
||||||
currentPage: paginationData.currentPage,
|
currentPage: paginationData.currentPage,
|
||||||
@ -98,10 +98,10 @@ const getTableData = () => {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const handleSearch = () => {
|
function handleSearch() {
|
||||||
paginationData.currentPage === 1 ? getTableData() : (paginationData.currentPage = 1)
|
paginationData.currentPage === 1 ? getTableData() : (paginationData.currentPage = 1)
|
||||||
}
|
}
|
||||||
const resetSearch = () => {
|
function resetSearch() {
|
||||||
searchFormRef.value?.resetFields()
|
searchFormRef.value?.resetFields()
|
||||||
handleSearch()
|
handleSearch()
|
||||||
}
|
}
|
||||||
@ -122,16 +122,24 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
|
|||||||
<el-input v-model="searchData.phone" placeholder="请输入" />
|
<el-input v-model="searchData.phone" placeholder="请输入" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
|
<el-button type="primary" :icon="Search" @click="handleSearch">
|
||||||
<el-button :icon="Refresh" @click="resetSearch">重置</el-button>
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button :icon="Refresh" @click="resetSearch">
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card v-loading="loading" shadow="never">
|
<el-card v-loading="loading" shadow="never">
|
||||||
<div class="toolbar-wrapper">
|
<div class="toolbar-wrapper">
|
||||||
<div>
|
<div>
|
||||||
<el-button type="primary" :icon="CirclePlus" @click="dialogVisible = true">新增用户</el-button>
|
<el-button type="primary" :icon="CirclePlus" @click="dialogVisible = true">
|
||||||
<el-button type="danger" :icon="Delete">批量删除</el-button>
|
新增用户
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" :icon="Delete">
|
||||||
|
批量删除
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<el-tooltip content="下载">
|
<el-tooltip content="下载">
|
||||||
@ -151,22 +159,32 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
|
|||||||
<el-tag v-if="scope.row.roles === 'admin'" type="primary" effect="plain" disable-transitions>
|
<el-tag v-if="scope.row.roles === 'admin'" type="primary" effect="plain" disable-transitions>
|
||||||
admin
|
admin
|
||||||
</el-tag>
|
</el-tag>
|
||||||
<el-tag v-else type="warning" effect="plain" disable-transitions>{{ scope.row.roles }}</el-tag>
|
<el-tag v-else type="warning" effect="plain" disable-transitions>
|
||||||
|
{{ scope.row.roles }}
|
||||||
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="phone" label="手机号" align="center" />
|
<el-table-column prop="phone" label="手机号" align="center" />
|
||||||
<el-table-column prop="email" label="邮箱" align="center" />
|
<el-table-column prop="email" label="邮箱" align="center" />
|
||||||
<el-table-column prop="status" label="状态" align="center">
|
<el-table-column prop="status" label="状态" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.status" type="success" effect="plain" disable-transitions>启用</el-tag>
|
<el-tag v-if="scope.row.status" type="success" effect="plain" disable-transitions>
|
||||||
<el-tag v-else type="danger" effect="plain" disable-transitions>禁用</el-tag>
|
启用
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-else type="danger" effect="plain" disable-transitions>
|
||||||
|
禁用
|
||||||
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="createTime" label="创建时间" align="center" />
|
<el-table-column prop="createTime" label="创建时间" align="center" />
|
||||||
<el-table-column fixed="right" label="操作" width="150" align="center">
|
<el-table-column fixed="right" label="操作" width="150" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" text bg size="small" @click="handleUpdate(scope.row)">修改</el-button>
|
<el-button type="primary" text bg size="small" @click="handleUpdate(scope.row)">
|
||||||
<el-button type="danger" text bg size="small" @click="handleDelete(scope.row)">删除</el-button>
|
修改
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" text bg size="small" @click="handleDelete(scope.row)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -178,7 +196,7 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
|
|||||||
:page-sizes="paginationData.pageSizes"
|
:page-sizes="paginationData.pageSizes"
|
||||||
:total="paginationData.total"
|
:total="paginationData.total"
|
||||||
:page-size="paginationData.pageSize"
|
:page-size="paginationData.pageSize"
|
||||||
:currentPage="paginationData.currentPage"
|
:current-page="paginationData.currentPage"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
/>
|
/>
|
||||||
@ -188,20 +206,24 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="formData.id === undefined ? '新增用户' : '修改用户'"
|
:title="formData.id === undefined ? '新增用户' : '修改用户'"
|
||||||
@closed="resetForm"
|
|
||||||
width="30%"
|
width="30%"
|
||||||
|
@closed="resetForm"
|
||||||
>
|
>
|
||||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" label-position="left">
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" label-position="left">
|
||||||
<el-form-item prop="username" label="用户名">
|
<el-form-item prop="username" label="用户名">
|
||||||
<el-input v-model="formData.username" placeholder="请输入" />
|
<el-input v-model="formData.username" placeholder="请输入" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="password" label="密码" v-if="formData.id === undefined">
|
<el-form-item v-if="formData.id === undefined" prop="password" label="密码">
|
||||||
<el-input v-model="formData.password" placeholder="请输入" />
|
<el-input v-model="formData.password" placeholder="请输入" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button @click="dialogVisible = false">
|
||||||
<el-button type="primary" @click="handleCreateOrUpdate" :loading="loading">确认</el-button>
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" :loading="loading" @click="handleCreateOrUpdate">
|
||||||
|
确认
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { nextTick, reactive, ref } from "vue"
|
import type { TableResponseData } from "@/api/table/types/table"
|
||||||
import { type ElMessageBoxOptions, ElMessageBox, ElMessage } from "element-plus"
|
import type { ElMessageBoxOptions } from "element-plus"
|
||||||
|
import type { VxeFormInstance, VxeFormProps, VxeGridInstance, VxeGridProps, VxeModalInstance, VxeModalProps } from "vxe-table"
|
||||||
import { deleteTableDataApi, getTableDataApi } from "@/api/table"
|
import { deleteTableDataApi, getTableDataApi } from "@/api/table"
|
||||||
import { type TableResponseData } from "@/api/table/types/table"
|
import { ElMessage, ElMessageBox } from "element-plus"
|
||||||
|
import { nextTick, reactive, ref } from "vue"
|
||||||
import RoleColumnSolts from "./tsx/RoleColumnSolts"
|
import RoleColumnSolts from "./tsx/RoleColumnSolts"
|
||||||
import StatusColumnSolts from "./tsx/StatusColumnSolts"
|
import StatusColumnSolts from "./tsx/StatusColumnSolts"
|
||||||
import {
|
|
||||||
type VxeGridInstance,
|
|
||||||
type VxeGridProps,
|
|
||||||
type VxeModalInstance,
|
|
||||||
type VxeModalProps,
|
|
||||||
type VxeFormInstance,
|
|
||||||
type VxeFormProps
|
|
||||||
} from "vxe-table"
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
// 命名当前组件
|
// 命名当前组件
|
||||||
@ -355,13 +349,21 @@ const crudStore = reactive({
|
|||||||
<vxe-grid ref="xGridDom" v-bind="xGridOpt">
|
<vxe-grid ref="xGridDom" v-bind="xGridOpt">
|
||||||
<!-- 左侧按钮列表 -->
|
<!-- 左侧按钮列表 -->
|
||||||
<template #toolbar-btns>
|
<template #toolbar-btns>
|
||||||
<vxe-button status="primary" icon="vxe-icon-add" @click="crudStore.onShowModal()">新增用户</vxe-button>
|
<vxe-button status="primary" icon="vxe-icon-add" @click="crudStore.onShowModal()">
|
||||||
<vxe-button status="danger" icon="vxe-icon-delete">批量删除</vxe-button>
|
新增用户
|
||||||
|
</vxe-button>
|
||||||
|
<vxe-button status="danger" icon="vxe-icon-delete">
|
||||||
|
批量删除
|
||||||
|
</vxe-button>
|
||||||
</template>
|
</template>
|
||||||
<!-- 操作 -->
|
<!-- 操作 -->
|
||||||
<template #row-operate="{ row }">
|
<template #row-operate="{ row }">
|
||||||
<el-button link type="primary" @click="crudStore.onShowModal(row)">修改</el-button>
|
<el-button link type="primary" @click="crudStore.onShowModal(row)">
|
||||||
<el-button link type="danger" @click="crudStore.onDelete(row)">删除</el-button>
|
修改
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="danger" @click="crudStore.onDelete(row)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</vxe-grid>
|
</vxe-grid>
|
||||||
<!-- 弹窗 -->
|
<!-- 弹窗 -->
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type VxeColumnPropTypes } from "vxe-table/types/column"
|
import type { VxeColumnPropTypes } from "vxe-table/types/column"
|
||||||
|
|
||||||
const solts: VxeColumnPropTypes.Slots = {
|
const solts: VxeColumnPropTypes.Slots = {
|
||||||
default: ({ row, column }) => {
|
default: ({ row, column }) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type VxeColumnPropTypes } from "vxe-table/types/column"
|
import type { VxeColumnPropTypes } from "vxe-table/types/column"
|
||||||
|
|
||||||
const solts: VxeColumnPropTypes.Slots = {
|
const solts: VxeColumnPropTypes.Slots = {
|
||||||
default: ({ row, column }) => {
|
default: ({ row, column }) => {
|
||||||
|
@ -2,10 +2,16 @@
|
|||||||
<div h-full uno-padding-20>
|
<div h-full uno-padding-20>
|
||||||
<div h-full text-center flex select-none all:transition-400>
|
<div h-full text-center flex select-none all:transition-400>
|
||||||
<div ma>
|
<div ma>
|
||||||
<div text-5xl fw100 animate-bounce-alt animate-count-infinite animate-1s>UnoCSS</div>
|
<div text-5xl fw100 animate-bounce-alt animate-count-infinite animate-1s>
|
||||||
<div op30 dark:op60 text-lg fw300 m1>该页面是一个 UnoCSS 的使用案例,其他页面依旧采用 Scss</div>
|
UnoCSS
|
||||||
|
</div>
|
||||||
|
<div op30 dark:op60 text-lg fw300 m1>
|
||||||
|
该页面是一个 UnoCSS 的使用案例,其他页面依旧采用 Scss
|
||||||
|
</div>
|
||||||
<div m2 flex justify-center text-lg op30 dark:op60 hover="op80" dark:hover="op80">
|
<div m2 flex justify-center text-lg op30 dark:op60 hover="op80" dark:hover="op80">
|
||||||
<a href="https://antfu.me/posts/reimagine-atomic-css-zh" target="_blank">推荐阅读:重新构想原子化 CSS</a>
|
<a href="https://antfu.me/posts/reimagine-atomic-css-zh" target="_blank">
|
||||||
|
推荐阅读:重新构想原子化 CSS
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { shallowMount } from "@vue/test-utils"
|
|
||||||
import { describe, expect, it } from "vitest"
|
|
||||||
import Notify from "@/components/Notify/index.vue"
|
import Notify from "@/components/Notify/index.vue"
|
||||||
import NotifyList from "@/components/Notify/NotifyList.vue"
|
import NotifyList from "@/components/Notify/NotifyList.vue"
|
||||||
|
import { shallowMount } from "@vue/test-utils"
|
||||||
|
import { describe, expect, it } from "vitest"
|
||||||
|
|
||||||
describe("Notify", () => {
|
describe("notify", () => {
|
||||||
it("正常渲染", () => {
|
it("正常渲染", () => {
|
||||||
const wrapper = shallowMount(Notify)
|
const wrapper = shallowMount(Notify)
|
||||||
expect(wrapper.classes("notify")).toBe(true)
|
expect(wrapper.classes("notify")).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("NotifyList", () => {
|
describe("notifyList", () => {
|
||||||
it("List 长度为 0", () => {
|
it("list 长度为 0", () => {
|
||||||
const wrapper = shallowMount(NotifyList, {
|
const wrapper = shallowMount(NotifyList, {
|
||||||
props: {
|
props: {
|
||||||
list: []
|
list: []
|
||||||
@ -19,7 +19,7 @@ describe("NotifyList", () => {
|
|||||||
})
|
})
|
||||||
expect(wrapper.find("el-empty").exists()).toBe(true)
|
expect(wrapper.find("el-empty").exists()).toBe(true)
|
||||||
})
|
})
|
||||||
it("List 长度不为 0", () => {
|
it("list 长度不为 0", () => {
|
||||||
const wrapper = shallowMount(NotifyList, {
|
const wrapper = shallowMount(NotifyList, {
|
||||||
props: {
|
props: {
|
||||||
list: [
|
list: [
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import { describe, expect, it } from "vitest"
|
|
||||||
import { isArray } from "@/utils/validate"
|
import { isArray } from "@/utils/validate"
|
||||||
|
import { describe, expect, it } from "vitest"
|
||||||
|
|
||||||
describe("isArray", () => {
|
describe("isArray", () => {
|
||||||
it("String", () => {
|
it("string", () => {
|
||||||
expect(isArray("")).toBe(false)
|
expect(isArray("")).toBe(false)
|
||||||
})
|
})
|
||||||
it("Number", () => {
|
it("number", () => {
|
||||||
expect(isArray(1)).toBe(false)
|
expect(isArray(1)).toBe(false)
|
||||||
})
|
})
|
||||||
it("Boolean", () => {
|
it("boolean", () => {
|
||||||
expect(isArray(true)).toBe(false)
|
expect(isArray(true)).toBe(false)
|
||||||
})
|
})
|
||||||
it("Null", () => {
|
it("null", () => {
|
||||||
expect(isArray(null)).toBe(false)
|
expect(isArray(null)).toBe(false)
|
||||||
})
|
})
|
||||||
it("Undefined", () => {
|
it("undefined", () => {
|
||||||
expect(isArray(undefined)).toBe(false)
|
expect(isArray(undefined)).toBe(false)
|
||||||
})
|
})
|
||||||
it("Symbol", () => {
|
it("symbol", () => {
|
||||||
expect(isArray(Symbol())).toBe(false)
|
expect(isArray(Symbol())).toBe(false)
|
||||||
})
|
})
|
||||||
it("BigInt", () => {
|
it("bigInt", () => {
|
||||||
expect(isArray(BigInt(1))).toBe(false)
|
expect(isArray(BigInt(1))).toBe(false)
|
||||||
})
|
})
|
||||||
it("Object", () => {
|
it("object", () => {
|
||||||
expect(isArray({})).toBe(false)
|
expect(isArray({})).toBe(false)
|
||||||
})
|
})
|
||||||
it("Array Object", () => {
|
it("array object", () => {
|
||||||
expect(isArray([])).toBe(true)
|
expect(isArray([])).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,24 +1,21 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
/** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
/** TS 严格模式 */
|
|
||||||
"strict": true,
|
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"jsxImportSource": "vue",
|
"jsxImportSource": "vue",
|
||||||
"importHelpers": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
/** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */
|
|
||||||
"isolatedModules": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"lib": ["esnext", "dom"],
|
"lib": ["esnext", "dom"],
|
||||||
"skipLibCheck": true,
|
/** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
/** baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项 */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
/** 非相对模块导入的路径映射配置,根据 baseUrl 配置进行路径计算 */
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"resolveJsonModule": true,
|
||||||
"types": [
|
"types": [
|
||||||
"node",
|
"node",
|
||||||
"vite/client",
|
"vite/client",
|
||||||
@ -26,12 +23,15 @@
|
|||||||
"element-plus/global",
|
"element-plus/global",
|
||||||
"vitest"
|
"vitest"
|
||||||
],
|
],
|
||||||
/** baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项 */
|
/** TS 严格模式 */
|
||||||
"baseUrl": ".",
|
"strict": true,
|
||||||
/** 非相对模块导入的路径映射配置,根据 baseUrl 配置进行路径计算 */
|
"importHelpers": true,
|
||||||
"paths": {
|
"sourceMap": true,
|
||||||
"@/*": ["src/*"]
|
"allowSyntheticDefaultImports": true,
|
||||||
}
|
"esModuleInterop": true,
|
||||||
|
/** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */
|
||||||
|
"isolatedModules": true,
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
|
2
types/global-components.d.ts
vendored
2
types/global-components.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import SvgIcon from "@/components/SvgIcon/index.vue"
|
import type SvgIcon from "@/components/SvgIcon/index.vue"
|
||||||
|
|
||||||
/** 由 app.component 全局注册的组件需要在这里声明 TS 类型才能获得 Volar 插件提供的类型提示) */
|
/** 由 app.component 全局注册的组件需要在这里声明 TS 类型才能获得 Volar 插件提供的类型提示) */
|
||||||
declare module "vue" {
|
declare module "vue" {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
|
|
||||||
import { type ConfigEnv, type UserConfigExport, loadEnv } from "vite"
|
import type { ConfigEnv, UserConfigExport } from "vite"
|
||||||
import path, { resolve } from "path"
|
import path, { resolve } from "node:path"
|
||||||
import vue from "@vitejs/plugin-vue"
|
import vue from "@vitejs/plugin-vue"
|
||||||
import vueJsx from "@vitejs/plugin-vue-jsx"
|
import vueJsx from "@vitejs/plugin-vue-jsx"
|
||||||
|
import UnoCSS from "unocss/vite"
|
||||||
|
import { loadEnv } from "vite"
|
||||||
import { createSvgIconsPlugin } from "vite-plugin-svg-icons"
|
import { createSvgIconsPlugin } from "vite-plugin-svg-icons"
|
||||||
import svgLoader from "vite-svg-loader"
|
import svgLoader from "vite-svg-loader"
|
||||||
import UnoCSS from "unocss/vite"
|
|
||||||
|
|
||||||
/** 配置项文档:https://cn.vitejs.dev/config */
|
/** 配置项文档:https://cn.vitejs.dev/config */
|
||||||
export default ({ mode }: ConfigEnv): UserConfigExport => {
|
export default ({ mode }: ConfigEnv): UserConfigExport => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user