From a0afedf5f32835e2b895861147b4891a7912493b Mon Sep 17 00:00:00 2001 From: molong Date: Sat, 10 Jan 2026 10:10:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc | 4 +- .stylelintrc.cjs | 160 +- index.html | 78 +- package.json | 222 ++- src/App.vue | 50 +- src/api/auth.ts | 26 +- src/api/system-manage.ts | 22 +- src/assets/styles/core/app.scss | 376 ++--- src/assets/styles/core/dark.scss | 138 +- src/assets/styles/core/el-light.scss | 58 +- src/assets/styles/core/el-ui.scss | 572 +++---- src/assets/styles/core/md.scss | 834 +++++----- src/assets/styles/core/mixin.scss | 199 +-- src/assets/styles/core/reset.scss | 32 +- src/assets/styles/core/router-transition.scss | 122 +- src/assets/styles/core/tailwind.css | 288 ++-- src/assets/styles/core/theme-animation.scss | 76 +- src/assets/styles/core/theme-change.scss | 14 +- src/assets/styles/custom/one-dark-pro.scss | 40 +- .../core/banners/art-basic-banner/index.vue | 613 ++++---- .../core/banners/art-card-banner/index.vue | 204 +-- .../core/base/art-back-to-top/index.vue | 60 +- src/components/core/base/art-logo/index.vue | 24 +- .../core/base/art-svg-icon/index.vue | 26 +- .../core/cards/art-bar-chart-card/index.vue | 189 +-- .../core/cards/art-data-list-card/index.vue | 122 +- .../core/cards/art-donut-chart-card/index.vue | 224 +-- .../core/cards/art-image-card/index.vue | 154 +- .../core/cards/art-line-chart-card/index.vue | 234 +-- .../core/cards/art-progress-card/index.vue | 143 +- .../core/cards/art-stats-card/index.vue | 122 +- .../cards/art-timeline-list-card/index.vue | 118 +- .../core/charts/art-bar-chart/index.vue | 358 ++--- .../art-dual-bar-compare-chart/index.vue | 344 ++--- .../core/charts/art-h-bar-chart/index.vue | 368 ++--- .../core/charts/art-k-line-chart/index.vue | 268 ++-- .../core/charts/art-line-chart/index.vue | 656 ++++---- .../core/charts/art-radar-chart/index.vue | 191 +-- .../core/charts/art-ring-chart/index.vue | 236 +-- .../core/charts/art-scatter-chart/index.vue | 211 +-- .../core/forms/art-button-more/index.vue | 117 +- .../core/forms/art-button-table/index.vue | 92 +- .../core/forms/art-drag-verify/index.vue | 749 ++++----- .../core/forms/art-excel-export/index.vue | 648 ++++---- .../core/forms/art-excel-import/index.vue | 100 +- src/components/core/forms/art-form/index.vue | 567 +++---- .../core/forms/art-search-bar/index.vue | 789 +++++----- .../core/forms/art-wang-editor/index.vue | 369 ++--- .../core/forms/art-wang-editor/style.scss | 328 ++-- .../core/layouts/art-breadcrumb/index.vue | 230 +-- .../core/layouts/art-chat-window/index.vue | 493 +++--- .../core/layouts/art-fast-enter/index.vue | 196 +-- .../layouts/art-fireworks-effect/index.vue | 1264 ++++++++-------- .../layouts/art-global-component/index.vue | 16 +- .../core/layouts/art-global-search/index.vue | 717 ++++----- .../core/layouts/art-header-bar/index.vue | 830 +++++----- .../art-header-bar/widget/ArtUserMenu.vue | 272 ++-- .../art-menus/art-horizontal-menu/index.vue | 178 +-- .../widget/HorizontalSubmenu.vue | 158 +- .../art-menus/art-mixed-menu/index.vue | 439 +++--- .../art-menus/art-sidebar-menu/index.vue | 619 ++++---- .../art-menus/art-sidebar-menu/style.scss | 418 ++--- .../art-menus/art-sidebar-menu/theme.scss | 354 ++--- .../widget/SidebarSubmenu.vue | 331 ++-- .../core/layouts/art-notification/index.vue | 777 +++++----- .../core/layouts/art-page-content/index.vue | 228 +-- .../core/layouts/art-screen-lock/index.vue | 913 +++++------ .../composables/useSettingsConfig.ts | 448 +++--- .../composables/useSettingsHandlers.ts | 266 ++-- .../composables/useSettingsPanel.ts | 328 ++-- .../composables/useSettingsState.ts | 52 +- .../core/layouts/art-settings-panel/index.vue | 108 +- .../layouts/art-settings-panel/style.scss | 130 +- .../widget/BasicSettings.vue | 134 +- .../widget/BoxStyleSettings.vue | 62 +- .../widget/ColorSettings.vue | 58 +- .../widget/ContainerSettings.vue | 54 +- .../widget/MenuLayoutSettings.vue | 53 +- .../widget/MenuStyleSettings.vue | 72 +- .../widget/SectionTitle.vue | 22 +- .../widget/SettingActions.vue | 412 ++--- .../widget/SettingDrawer.vue | 80 +- .../widget/SettingHeader.vue | 26 +- .../art-settings-panel/widget/SettingItem.vue | 168 ++- .../widget/ThemeSettings.vue | 44 +- .../core/layouts/art-work-tab/index.vue | 1127 +++++++------- .../core/media/art-cutter-img/index.vue | 592 ++++---- .../core/media/art-video-player/index.vue | 186 +-- .../core/others/art-menu-right/index.vue | 709 ++++----- .../core/others/art-watermark/index.vue | 108 +- .../core/tables/art-table-header/index.vue | 585 +++---- .../core/tables/art-table/index.vue | 589 ++++---- .../core/tables/art-table/style.scss | 156 +- .../core/text-effect/art-count-to/index.vue | 535 +++---- .../art-festival-text-scroll/index.vue | 46 +- .../text-effect/art-text-scroll/index.vue | 478 +++--- src/components/core/theme/theme-svg/index.vue | 154 +- .../core/views/exception/ArtException.vue | 66 +- .../core/views/login/AuthTopBar.vue | 268 ++-- .../core/views/login/LoginLeftView.vue | 1181 ++++++++------- .../core/views/result/ArtResultPage.vue | 72 +- .../core/widget/art-icon-button/index.vue | 30 +- src/config/assets/images.ts | 58 +- src/config/fastEnter.ts | 136 +- src/config/index.ts | 192 +-- src/config/modules/component.ts | 120 +- src/config/modules/fastEnter.ts | 232 +-- src/config/modules/festival.ts | 36 +- src/config/modules/headerBar.ts | 88 +- src/config/setting.ts | 130 +- src/directives/business/highlight.ts | 288 ++-- src/directives/business/ripple.ts | 110 +- src/directives/core/auth.ts | 30 +- src/directives/core/roles.ts | 44 +- src/directives/index.ts | 8 +- src/enums/appEnum.ts | 60 +- src/enums/formEnum.ts | 10 +- src/env.d.ts | 38 +- src/hooks/core/useAppMode.ts | 42 +- src/hooks/core/useAuth.ts | 50 +- src/hooks/core/useCeremony.ts | 214 +-- src/hooks/core/useChart.ts | 1170 +++++++------- src/hooks/core/useCommon.ts | 116 +- src/hooks/core/useFastEnter.ts | 52 +- src/hooks/core/useHeaderBar.ts | 292 ++-- src/hooks/core/useLayoutHeight.ts | 188 +-- src/hooks/core/useTable.ts | 1342 +++++++++-------- src/hooks/core/useTableColumns.ts | 459 +++--- src/hooks/core/useTableHeight.ts | 109 +- src/hooks/core/useTheme.ts | 209 +-- src/locales/index.ts | 76 +- src/locales/langs/en.json | 569 ++++--- src/locales/langs/zh.json | 569 ++++--- src/mock/temp/formData.ts | 498 +++--- src/mock/upgrade/changeLog.ts | 12 +- src/plugins/echarts.ts | 80 +- src/router/core/ComponentLoader.ts | 118 +- src/router/core/IframeRouteManager.ts | 114 +- src/router/core/MenuProcessor.ts | 382 ++--- src/router/core/RoutePermissionValidator.ts | 158 +- src/router/core/RouteRegistry.ts | 126 +- src/router/core/RouteTransformer.ts | 198 +-- src/router/core/RouteValidator.ts | 294 ++-- src/router/guards/afterEach.ts | 40 +- src/router/guards/beforeEach.ts | 416 ++--- src/router/index.ts | 12 +- src/router/modules/dashboard.ts | 40 +- src/router/modules/exception.ts | 84 +- src/router/modules/index.ts | 8 +- src/router/modules/result.ts | 58 +- src/router/modules/system.ts | 112 +- src/router/routes/staticRoutes.ts | 116 +- src/router/routesAlias.ts | 4 +- src/store/index.ts | 18 +- src/store/modules/menu.ts | 120 +- src/store/modules/setting.ts | 726 ++++----- src/store/modules/table.ts | 112 +- src/store/modules/user.ts | 336 ++--- src/store/modules/worktab.ts | 1032 ++++++------- src/types/api/api.d.ts | 177 +-- src/types/common/index.ts | 28 +- src/types/common/response.ts | 12 +- src/types/component/chart.ts | 396 +++-- src/types/component/index.ts | 200 +-- src/types/config/index.ts | 264 ++-- src/types/router/index.ts | 86 +- src/types/store/index.ts | 180 +-- src/utils/constants/links.ts | 36 +- src/utils/form/responsive.ts | 46 +- src/utils/form/validator.ts | 286 ++-- src/utils/http/error.ts | 196 +-- src/utils/http/index.ts | 234 +-- src/utils/http/status.ts | 26 +- src/utils/navigation/jump.ts | 52 +- src/utils/navigation/route.ts | 42 +- src/utils/navigation/worktab.ts | 60 +- src/utils/router.ts | 52 +- src/utils/socket/index.ts | 675 ++++----- src/utils/storage/storage-config.ts | 152 +- src/utils/storage/storage-key-manager.ts | 100 +- src/utils/storage/storage.ts | 318 ++-- src/utils/sys/error-handle.ts | 80 +- src/utils/sys/mittBus.ts | 20 +- src/utils/sys/upgrade.ts | 382 ++--- src/utils/table/tableCache.ts | 362 ++--- src/utils/table/tableConfig.ts | 34 +- src/utils/table/tableUtils.ts | 358 ++--- src/utils/ui/animation.ts | 46 +- src/utils/ui/colors.ts | 204 +-- src/utils/ui/emojo.ts | 8 +- src/utils/ui/loading.ts | 68 +- src/utils/ui/tabs.ts | 38 +- src/views/auth/forget-password/index.vue | 92 +- src/views/auth/login/index.vue | 480 +++--- src/views/auth/login/style.css | 42 +- src/views/auth/register/index.vue | 412 ++--- src/views/dashboard/console/index.vue | 62 +- .../console/modules/about-project.vue | 70 +- .../dashboard/console/modules/active-user.vue | 80 +- .../dashboard/console/modules/card-list.vue | 140 +- .../console/modules/dynamic-stats.vue | 144 +- .../dashboard/console/modules/new-user.vue | 312 ++-- .../console/modules/sales-overview.vue | 74 +- .../dashboard/console/modules/todo-list.vue | 128 +- src/views/exception/403/index.vue | 20 +- src/views/exception/404/index.vue | 20 +- src/views/exception/500/index.vue | 20 +- src/views/index/index.vue | 36 +- src/views/index/style.scss | 146 +- src/views/outside/Iframe.vue | 64 +- src/views/result/fail/index.vue | 46 +- src/views/result/success/index.vue | 32 +- src/views/system/menu/index.vue | 860 +++++------ src/views/system/menu/modules/menu-dialog.vue | 721 ++++----- src/views/system/role/index.vue | 438 +++--- .../system/role/modules/role-edit-dialog.vue | 284 ++-- .../role/modules/role-permission-dialog.vue | 438 +++--- src/views/system/role/modules/role-search.vue | 214 +-- src/views/system/user-center/index.vue | 449 +++--- src/views/system/user/index.vue | 476 +++--- src/views/system/user/modules/user-dialog.vue | 248 +-- src/views/system/user/modules/user-search.vue | 198 +-- tsconfig.json | 52 +- vite.config.ts | 266 ++-- 224 files changed, 27320 insertions(+), 26980 deletions(-) diff --git a/.prettierrc b/.prettierrc index f3d6ad5..7af6cc9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,7 @@ { "printWidth": 100, - "tabWidth": 2, - "useTabs": false, + "tabWidth": 4, + "useTabs": true, "semi": false, "vueIndentScriptAndStyle": true, "singleQuote": true, diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs index 9dbea0b..cf344d4 100644 --- a/.stylelintrc.cjs +++ b/.stylelintrc.cjs @@ -1,82 +1,82 @@ module.exports = { - // 继承推荐规范配置 - extends: [ - 'stylelint-config-standard', - 'stylelint-config-recommended-scss', - 'stylelint-config-recommended-vue/scss', - 'stylelint-config-html/vue', - 'stylelint-config-recess-order' - ], - // 指定不同文件对应的解析器 - overrides: [ - { - files: ['**/*.{vue,html}'], - customSyntax: 'postcss-html' - }, - { - files: ['**/*.{css,scss}'], - customSyntax: 'postcss-scss' - } - ], - // 自定义规则 - rules: { - 'import-notation': 'string', // 指定导入CSS文件的方式("string"|"url") - 'selector-class-pattern': null, // 选择器类名命名规则 - 'custom-property-pattern': null, // 自定义属性命名规则 - 'keyframes-name-pattern': null, // 动画帧节点样式命名规则 - 'no-descending-specificity': null, // 允许无降序特异性 - 'no-empty-source': null, // 允许空样式 - 'property-no-vendor-prefix': null, // 允许属性前缀 - // 允许 global 、export 、deep伪类 - 'selector-pseudo-class-no-unknown': [ - true, - { - ignorePseudoClasses: ['global', 'export', 'deep'] - } - ], - // 允许未知属性 - 'property-no-unknown': [ - true, - { - ignoreProperties: [] - } - ], - // 允许未知规则 - 'at-rule-no-unknown': [ - true, - { - ignoreAtRules: [ - 'apply', - 'use', - 'mixin', - 'include', - 'extend', - 'each', - 'if', - 'else', - 'for', - 'while', - 'reference' - ] - } - ], - 'scss/at-rule-no-unknown': [ - true, - { - ignoreAtRules: [ - 'apply', - 'use', - 'mixin', - 'include', - 'extend', - 'each', - 'if', - 'else', - 'for', - 'while', - 'reference' - ] - } - ] - } + // 继承推荐规范配置 + extends: [ + 'stylelint-config-standard', + 'stylelint-config-recommended-scss', + 'stylelint-config-recommended-vue/scss', + 'stylelint-config-html/vue', + 'stylelint-config-recess-order' + ], + // 指定不同文件对应的解析器 + overrides: [ + { + files: ['**/*.{vue,html}'], + customSyntax: 'postcss-html' + }, + { + files: ['**/*.{css,scss}'], + customSyntax: 'postcss-scss' + } + ], + // 自定义规则 + rules: { + 'import-notation': 'string', // 指定导入CSS文件的方式("string"|"url") + 'selector-class-pattern': null, // 选择器类名命名规则 + 'custom-property-pattern': null, // 自定义属性命名规则 + 'keyframes-name-pattern': null, // 动画帧节点样式命名规则 + 'no-descending-specificity': null, // 允许无降序特异性 + 'no-empty-source': null, // 允许空样式 + 'property-no-vendor-prefix': null, // 允许属性前缀 + // 允许 global 、export 、deep伪类 + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global', 'export', 'deep'] + } + ], + // 允许未知属性 + 'property-no-unknown': [ + true, + { + ignoreProperties: [] + } + ], + // 允许未知规则 + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'apply', + 'use', + 'mixin', + 'include', + 'extend', + 'each', + 'if', + 'else', + 'for', + 'while', + 'reference' + ] + } + ], + 'scss/at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'apply', + 'use', + 'mixin', + 'include', + 'extend', + 'each', + 'if', + 'else', + 'for', + 'while', + 'reference' + ] + } + ] + } } diff --git a/index.html b/index.html index ba51ce5..9983b8c 100644 --- a/index.html +++ b/index.html @@ -1,47 +1,47 @@ - - Art Design Pro - - - - + + Art Design Pro + + + + - + html.dark { + background-color: #070707; + } + - - + const themeType = localStorage.getItem('sys-theme') + if (themeType === 'dark') { + document.documentElement.classList.add('dark') + } + } catch (e) { + console.warn('Failed to apply initial theme:', e) + } + })() + + - -
- - + +
+ + diff --git a/package.json b/package.json index f1c5621..80da018 100644 --- a/package.json +++ b/package.json @@ -1,118 +1,108 @@ { - "name": "art-design-pro", - "version": "0.0.0", - "type": "module", - "engines": { - "node": ">=20.19.0", - "pnpm": ">=8.8.0" - }, - "scripts": { - "dev": "vite --open", - "build": "vue-tsc --noEmit && vite build", - "serve": "vite preview", - "lint": "eslint", - "fix": "eslint --fix", - "lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"", - "lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix", - "lint:lint-staged": "lint-staged", - "prepare": "husky", - "commit": "git-cz", - "clean:dev": "tsx scripts/clean-dev.ts" - }, - "config": { - "commitizen": { - "path": "node_modules/cz-git" - } - }, - "lint-staged": { - "*.{js,ts,mjs,mts,tsx}": [ - "eslint --fix", - "prettier --write" - ], - "*.{cjs,json,jsonc}": [ - "prettier --write" - ], - "*.vue": [ - "eslint --fix", - "stylelint --fix --allow-empty-input", - "prettier --write" - ], - "*.{html,htm}": [ - "prettier --write" - ], - "*.{scss,css,less}": [ - "stylelint --fix --allow-empty-input", - "prettier --write" - ], - "*.{md,mdx}": [ - "prettier --write" - ], - "*.{yaml,yml}": [ - "prettier --write" - ] - }, - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "@iconify/vue": "^5.0.0", - "@tailwindcss/vite": "^4.1.14", - "@vue/reactivity": "^3.5.21", - "@vueuse/core": "^13.9.0", - "@wangeditor/editor": "^5.1.23", - "@wangeditor/editor-for-vue": "next", - "axios": "^1.12.2", - "crypto-js": "^4.2.0", - "echarts": "^6.0.0", - "element-plus": "^2.11.2", - "file-saver": "^2.0.5", - "highlight.js": "^11.10.0", - "mitt": "^3.0.1", - "nprogress": "^0.2.0", - "ohash": "^2.0.11", - "pinia": "^3.0.3", - "pinia-plugin-persistedstate": "^4.3.0", - "qrcode.vue": "^3.6.0", - "tailwindcss": "^4.1.14", - "vue": "^3.5.21", - "vue-draggable-plus": "^0.6.0", - "vue-i18n": "^9.14.0", - "vue-router": "^4.5.1", - "xgplayer": "^3.0.20", - "xlsx": "^0.18.5" - }, - "devDependencies": { - "@eslint/js": "^9.9.1", - "@types/node": "^24.0.5", - "@typescript-eslint/eslint-plugin": "^8.3.0", - "@typescript-eslint/parser": "^8.3.0", - "@vitejs/plugin-vue": "^6.0.1", - "@vue/compiler-sfc": "^3.0.5", - "eslint": "^9.9.1", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-vue": "^9.27.0", - "globals": "^15.9.0", - "lint-staged": "^15.5.2", - "prettier": "^3.5.3", - "rollup-plugin-visualizer": "^5.12.0", - "sass": "^1.81.0", - "stylelint": "^16.20.0", - "stylelint-config-html": "^1.1.0", - "stylelint-config-recess-order": "^4.6.0", - "stylelint-config-recommended-scss": "^14.1.0", - "stylelint-config-recommended-vue": "^1.5.0", - "stylelint-config-standard": "^36.0.1", - "terser": "^5.36.0", - "tsx": "^4.20.3", - "typescript": "~5.6.3", - "typescript-eslint": "^8.9.0", - "unplugin-auto-import": "^20.2.0", - "unplugin-element-plus": "^0.10.0", - "unplugin-vue-components": "^29.1.0", - "vite": "^7.1.5", - "vite-plugin-compression": "^0.5.1", - "vite-plugin-vue-devtools": "^7.7.6", - "vue-demi": "^0.14.9", - "vue-img-cutter": "^3.0.5", - "vue-tsc": "~2.1.6" - } + "name": "art-design-pro", + "version": "0.1.0", + "type": "module", + "engines": { + "node": ">=20.19.0" + }, + "scripts": { + "dev": "vite --open", + "build": "vue-tsc --noEmit && vite build", + "serve": "vite preview", + "lint": "eslint", + "fix": "eslint --fix", + "lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"", + "lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix" + }, + "lint-staged": { + "*.{js,ts,mjs,mts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{cjs,json,jsonc}": [ + "prettier --write" + ], + "*.vue": [ + "eslint --fix", + "stylelint --fix --allow-empty-input", + "prettier --write" + ], + "*.{html,htm}": [ + "prettier --write" + ], + "*.{scss,css,less}": [ + "stylelint --fix --allow-empty-input", + "prettier --write" + ], + "*.{md,mdx}": [ + "prettier --write" + ], + "*.{yaml,yml}": [ + "prettier --write" + ] + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@iconify/vue": "^5.0.0", + "@tailwindcss/vite": "^4.1.14", + "@vue/reactivity": "^3.5.21", + "@vueuse/core": "^13.9.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "next", + "axios": "^1.12.2", + "crypto-js": "^4.2.0", + "echarts": "^6.0.0", + "element-plus": "^2.11.2", + "file-saver": "^2.0.5", + "highlight.js": "^11.10.0", + "mitt": "^3.0.1", + "nprogress": "^0.2.0", + "ohash": "^2.0.11", + "pinia": "^3.0.3", + "pinia-plugin-persistedstate": "^4.3.0", + "qrcode.vue": "^3.6.0", + "tailwindcss": "^4.1.14", + "vue": "^3.5.21", + "vue-draggable-plus": "^0.6.0", + "vue-i18n": "^9.14.0", + "vue-router": "^4.5.1", + "xgplayer": "^3.0.20", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@eslint/js": "^9.9.1", + "@types/node": "^24.0.5", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "@typescript-eslint/parser": "^8.3.0", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/compiler-sfc": "^3.0.5", + "eslint": "^9.9.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-vue": "^9.27.0", + "globals": "^15.9.0", + "lint-staged": "^15.5.2", + "prettier": "^3.5.3", + "rollup-plugin-visualizer": "^5.12.0", + "sass": "^1.81.0", + "stylelint": "^16.20.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-recess-order": "^4.6.0", + "stylelint-config-recommended-scss": "^14.1.0", + "stylelint-config-recommended-vue": "^1.5.0", + "stylelint-config-standard": "^36.0.1", + "terser": "^5.36.0", + "tsx": "^4.20.3", + "typescript": "~5.6.3", + "typescript-eslint": "^8.9.0", + "unplugin-auto-import": "^20.2.0", + "unplugin-element-plus": "^0.10.0", + "unplugin-vue-components": "^29.1.0", + "vite": "^7.1.5", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-vue-devtools": "^7.7.6", + "vue-demi": "^0.14.9", + "vue-img-cutter": "^3.0.5", + "vue-tsc": "~2.1.6" + } } diff --git a/src/App.vue b/src/App.vue index 3433913..2a4cef1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,34 +1,34 @@ diff --git a/src/api/auth.ts b/src/api/auth.ts index 9dc7b6a..e6a8de4 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -6,12 +6,12 @@ import request from '@/utils/http' * @returns 登录响应 */ export function fetchLogin(params: Api.Auth.LoginParams) { - return request.post({ - url: '/api/auth/login', - params - // showSuccessMessage: true // 显示成功消息 - // showErrorMessage: false // 不显示错误消息 - }) + return request.post({ + url: '/api/auth/login', + params + // showSuccessMessage: true // 显示成功消息 + // showErrorMessage: false // 不显示错误消息 + }) } /** @@ -19,11 +19,11 @@ export function fetchLogin(params: Api.Auth.LoginParams) { * @returns 用户信息 */ export function fetchGetUserInfo() { - return request.get({ - url: '/api/user/info' - // 自定义请求头 - // headers: { - // 'X-Custom-Header': 'your-custom-value' - // } - }) + return request.get({ + url: '/api/user/info' + // 自定义请求头 + // headers: { + // 'X-Custom-Header': 'your-custom-value' + // } + }) } diff --git a/src/api/system-manage.ts b/src/api/system-manage.ts index 8f4a8e6..8bf9c68 100644 --- a/src/api/system-manage.ts +++ b/src/api/system-manage.ts @@ -3,23 +3,23 @@ import { AppRouteRecord } from '@/types/router' // 获取用户列表 export function fetchGetUserList(params: Api.SystemManage.UserSearchParams) { - return request.get({ - url: '/api/user/list', - params - }) + return request.get({ + url: '/api/user/list', + params + }) } // 获取角色列表 export function fetchGetRoleList(params: Api.SystemManage.RoleSearchParams) { - return request.get({ - url: '/api/role/list', - params - }) + return request.get({ + url: '/api/role/list', + params + }) } // 获取菜单列表 export function fetchGetMenuList() { - return request.get({ - url: '/api/v3/system/menus/simple' - }) + return request.get({ + url: '/api/v3/system/menus/simple' + }) } diff --git a/src/assets/styles/core/app.scss b/src/assets/styles/core/app.scss index c0efeed..91283be 100644 --- a/src/assets/styles/core/app.scss +++ b/src/assets/styles/core/app.scss @@ -1,292 +1,292 @@ // 全局样式 // 顶部进度条颜色 #nprogress .bar { - z-index: 2400; - background-color: color-mix(in srgb, var(--theme-color) 70%, white); + z-index: 2400; + background-color: color-mix(in srgb, var(--theme-color) 70%, white); } #nprogress .peg { - box-shadow: - 0 0 10px var(--theme-color), - 0 0 5px var(--theme-color) !important; + box-shadow: + 0 0 10px var(--theme-color), + 0 0 5px var(--theme-color) !important; } #nprogress .spinner-icon { - border-top-color: var(--theme-color) !important; - border-left-color: var(--theme-color) !important; + border-top-color: var(--theme-color) !important; + border-left-color: var(--theme-color) !important; } // 处理移动端组件兼容性 @media screen and (max-width: 640px) { - * { - cursor: default !important; - } + * { + cursor: default !important; + } } // 背景滤镜 *, ::before, ::after { - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; } // 色弱模式 .color-weak { - filter: invert(80%); - -webkit-filter: invert(80%); + filter: invert(80%); + -webkit-filter: invert(80%); } #noop { - display: none; + display: none; } // 语言切换选中样式 .langDropDownStyle { - // 选中项背景颜色 - .is-selected { - background-color: var(--art-el-active-color) !important; - } + // 选中项背景颜色 + .is-selected { + background-color: var(--art-el-active-color) !important; + } - // 语言切换按钮菜单样式优化 - .lang-btn-item { - .el-dropdown-menu__item { - padding-left: 13px !important; - padding-right: 6px !important; - margin-bottom: 3px !important; - } + // 语言切换按钮菜单样式优化 + .lang-btn-item { + .el-dropdown-menu__item { + padding-left: 13px !important; + padding-right: 6px !important; + margin-bottom: 3px !important; + } - &:last-child { - .el-dropdown-menu__item { - margin-bottom: 0 !important; - } - } + &:last-child { + .el-dropdown-menu__item { + margin-bottom: 0 !important; + } + } - .menu-txt { - min-width: 60px; - display: block; - } + .menu-txt { + min-width: 60px; + display: block; + } - i { - font-size: 10px; - margin-left: 10px; - } - } + i { + font-size: 10px; + margin-left: 10px; + } + } } // 盒子默认边框 .page-content { - border: 1px solid var(--art-card-border) !important; + border: 1px solid var(--art-card-border) !important; } @mixin art-card-base($border-color, $shadow: none, $radius-diff: 4px) { - background: var(--default-box-color); - border: 1px solid #{$border-color} !important; - border-radius: calc(var(--custom-radius) + #{$radius-diff}) !important; - box-shadow: #{$shadow} !important; + background: var(--default-box-color); + border: 1px solid #{$border-color} !important; + border-radius: calc(var(--custom-radius) + #{$radius-diff}) !important; + box-shadow: #{$shadow} !important; - --el-card-border-color: var(--default-border) !important; + --el-card-border-color: var(--default-border) !important; } .art-card, .art-card-sm, .art-card-xs { - border: 1px solid var(--art-card-border); + border: 1px solid var(--art-card-border); } // 盒子边框 [data-box-mode='border-mode'] { - .page-content, - .art-table-card { - border: 1px solid var(--art-card-border) !important; - } + .page-content, + .art-table-card { + border: 1px solid var(--art-card-border) !important; + } - .art-card { - @include art-card-base(var(--art-card-border), none, 4px); - } + .art-card { + @include art-card-base(var(--art-card-border), none, 4px); + } - .art-card-sm { - @include art-card-base(var(--art-card-border), none, 0px); - } + .art-card-sm { + @include art-card-base(var(--art-card-border), none, 0px); + } - .art-card-xs { - @include art-card-base(var(--art-card-border), none, -4px); - } + .art-card-xs { + @include art-card-base(var(--art-card-border), none, -4px); + } } // 盒子阴影 [data-box-mode='shadow-mode'] { - .page-content, - .art-table-card { - box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.04) !important; - border: 1px solid var(--art-gray-200) !important; - } + .page-content, + .art-table-card { + box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.04) !important; + border: 1px solid var(--art-gray-200) !important; + } - .layout-sidebar { - border-right: 1px solid var(--art-card-border) !important; - } + .layout-sidebar { + border-right: 1px solid var(--art-card-border) !important; + } - .art-card { - @include art-card-base( - var(--art-gray-200), - (0 1px 3px 0 rgba(0, 0, 0, 0.03), 0 1px 2px -1px rgba(0, 0, 0, 0.08)), - 4px - ); - } + .art-card { + @include art-card-base( + var(--art-gray-200), + (0 1px 3px 0 rgba(0, 0, 0, 0.03), 0 1px 2px -1px rgba(0, 0, 0, 0.08)), + 4px + ); + } - .art-card-sm { - @include art-card-base( - var(--art-gray-200), - (0 1px 3px 0 rgba(0, 0, 0, 0.03), 0 1px 2px -1px rgba(0, 0, 0, 0.08)), - 2px - ); - } + .art-card-sm { + @include art-card-base( + var(--art-gray-200), + (0 1px 3px 0 rgba(0, 0, 0, 0.03), 0 1px 2px -1px rgba(0, 0, 0, 0.08)), + 2px + ); + } - .art-card-xs { - @include art-card-base( - var(--art-gray-200), - (0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 1px -1px rgba(0, 0, 0, 0.08)), - -4px - ); - } + .art-card-xs { + @include art-card-base( + var(--art-gray-200), + (0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 1px -1px rgba(0, 0, 0, 0.08)), + -4px + ); + } } // 元素全屏 .el-full-screen { - position: fixed; - top: 0; - left: 0; - right: 0; - width: 100vw !important; - height: 100% !important; - z-index: 2300; - margin-top: 0; - padding: 15px; - box-sizing: border-box; - background-color: var(--default-box-color); - display: flex; - flex-direction: column; + position: fixed; + top: 0; + left: 0; + right: 0; + width: 100vw !important; + height: 100% !important; + z-index: 2300; + margin-top: 0; + padding: 15px; + box-sizing: border-box; + background-color: var(--default-box-color); + display: flex; + flex-direction: column; } // 表格卡片 .art-table-card { - flex: 1; - display: flex; - flex-direction: column; - margin-top: 12px; - border-radius: calc(var(--custom-radius) / 2 + 2px) !important; + flex: 1; + display: flex; + flex-direction: column; + margin-top: 12px; + border-radius: calc(var(--custom-radius) / 2 + 2px) !important; - .el-card__body { - height: 100%; - overflow: hidden; - } + .el-card__body { + height: 100%; + overflow: hidden; + } } // 容器全高 .art-full-height { - height: var(--art-full-height); - display: flex; - flex-direction: column; + height: var(--art-full-height); + display: flex; + flex-direction: column; - @media (max-width: 640px) { - height: auto; - } + @media (max-width: 640px) { + height: auto; + } } // 徽章样式 .art-badge { - position: absolute; - top: 0; - right: 20px; - bottom: 0; - width: 6px; - height: 6px; - margin: auto; - background: #ff3860; - border-radius: 50%; - animation: breathe 1.5s ease-in-out infinite; + position: absolute; + top: 0; + right: 20px; + bottom: 0; + width: 6px; + height: 6px; + margin: auto; + background: #ff3860; + border-radius: 50%; + animation: breathe 1.5s ease-in-out infinite; - &.art-badge-horizontal { - right: 0; - } + &.art-badge-horizontal { + right: 0; + } - &.art-badge-mixed { - right: 0; - } + &.art-badge-mixed { + right: 0; + } - &.art-badge-dual { - right: 5px; - top: 5px; - bottom: auto; - } + &.art-badge-dual { + right: 5px; + top: 5px; + bottom: auto; + } } // 文字徽章样式 .art-text-badge { - position: absolute; - top: 0; - right: 12px; - bottom: 0; - min-width: 20px; - height: 18px; - line-height: 17px; - padding: 0 5px; - margin: auto; - font-size: 10px; - color: #fff; - text-align: center; - background: #fd4e4e; - border-radius: 4px; + position: absolute; + top: 0; + right: 12px; + bottom: 0; + min-width: 20px; + height: 18px; + line-height: 17px; + padding: 0 5px; + margin: auto; + font-size: 10px; + color: #fff; + text-align: center; + background: #fd4e4e; + border-radius: 4px; } @keyframes breathe { - 0% { - opacity: 0.7; - transform: scale(1); - } + 0% { + opacity: 0.7; + transform: scale(1); + } - 50% { - opacity: 1; - transform: scale(1.1); - } + 50% { + opacity: 1; + transform: scale(1.1); + } - 100% { - opacity: 0.7; - transform: scale(1); - } + 100% { + opacity: 0.7; + transform: scale(1); + } } // 修复老机型 loading 定位问题 .art-loading-fix { - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - width: 100vw !important; - height: 100vh !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100vw !important; + height: 100vh !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; } .art-loading-fix .el-loading-spinner { - position: static !important; - top: auto !important; - left: auto !important; - transform: none !important; + position: static !important; + top: auto !important; + left: auto !important; + transform: none !important; } // 去除移动端点击背景色 @media screen and (max-width: 1180px) { - * { - -webkit-tap-highlight-color: transparent; - } + * { + -webkit-tap-highlight-color: transparent; + } } diff --git a/src/assets/styles/core/dark.scss b/src/assets/styles/core/dark.scss index c52abc3..bbb53f2 100644 --- a/src/assets/styles/core/dark.scss +++ b/src/assets/styles/core/dark.scss @@ -7,87 +7,87 @@ $font-color: rgba(#ffffff, 0.85); /* 覆盖element-plus默认深色背景色 */ html.dark { - // element-plus - --el-bg-color: var(--default-box-color); - --el-text-color-regular: #{$font-color}; + // element-plus + --el-bg-color: var(--default-box-color); + --el-text-color-regular: #{$font-color}; - // 富文本编辑器 - // 工具栏背景颜色 - --w-e-toolbar-bg-color: #18191c; - // 输入区域背景颜色 - --w-e-textarea-bg-color: #090909; - // 工具栏文字颜色 - --w-e-toolbar-color: var(--art-gray-600); - // 选中菜单颜色 - --w-e-toolbar-active-bg-color: #25262b; - // 弹窗边框颜色 - --w-e-toolbar-border-color: var(--default-border-dashed); - // 分割线颜色 - --w-e-textarea-border-color: var(--default-border-dashed); - // 链接输入框边框颜色 - --w-e-modal-button-border-color: var(--default-border-dashed); - // 表格头颜色 - --w-e-textarea-slight-bg-color: #090909; - // 按钮背景颜色 - --w-e-modal-button-bg-color: #090909; - // hover toolbar 背景颜色 - --w-e-toolbar-active-color: var(--art-gray-800); + // 富文本编辑器 + // 工具栏背景颜色 + --w-e-toolbar-bg-color: #18191c; + // 输入区域背景颜色 + --w-e-textarea-bg-color: #090909; + // 工具栏文字颜色 + --w-e-toolbar-color: var(--art-gray-600); + // 选中菜单颜色 + --w-e-toolbar-active-bg-color: #25262b; + // 弹窗边框颜色 + --w-e-toolbar-border-color: var(--default-border-dashed); + // 分割线颜色 + --w-e-textarea-border-color: var(--default-border-dashed); + // 链接输入框边框颜色 + --w-e-modal-button-border-color: var(--default-border-dashed); + // 表格头颜色 + --w-e-textarea-slight-bg-color: #090909; + // 按钮背景颜色 + --w-e-modal-button-bg-color: #090909; + // hover toolbar 背景颜色 + --w-e-toolbar-active-color: var(--art-gray-800); } .dark { - .page-content .article-list .item .left .outer > div { - border-right-color: var(--dark-border-color) !important; - } + .page-content .article-list .item .left .outer > div { + border-right-color: var(--dark-border-color) !important; + } - // 富文本编辑器 - .editor-wrapper { - *:not(pre code *) { - color: inherit !important; - } - } - // 分隔线 - .w-e-bar-divider { - background-color: var(--art-gray-300) !important; - } + // 富文本编辑器 + .editor-wrapper { + *:not(pre code *) { + color: inherit !important; + } + } + // 分隔线 + .w-e-bar-divider { + background-color: var(--art-gray-300) !important; + } - .w-e-select-list, - .w-e-drop-panel, - .w-e-bar-item-group .w-e-bar-item-menus-container, - .w-e-text-container [data-slate-editor] pre > code { - border: 1px solid var(--default-border) !important; - } + .w-e-select-list, + .w-e-drop-panel, + .w-e-bar-item-group .w-e-bar-item-menus-container, + .w-e-text-container [data-slate-editor] pre > code { + border: 1px solid var(--default-border) !important; + } - // 下拉选择框 - .w-e-select-list { - background-color: var(--default-box-color) !important; - } + // 下拉选择框 + .w-e-select-list { + background-color: var(--default-box-color) !important; + } - /* 下拉选择框 hover 样式调整 */ - .w-e-select-list ul li:hover, + /* 下拉选择框 hover 样式调整 */ + .w-e-select-list ul li:hover, /* 工具栏 hover 按钮背景颜色 */ .w-e-bar-item button:hover { - background-color: #090909 !important; - } + background-color: #090909 !important; + } - /* 代码块 */ - .w-e-text-container [data-slate-editor] pre > code { - background-color: #25262b !important; - text-shadow: none !important; - } + /* 代码块 */ + .w-e-text-container [data-slate-editor] pre > code { + background-color: #25262b !important; + text-shadow: none !important; + } - /* 引用 */ - .w-e-text-container [data-slate-editor] blockquote { - border-left: 4px solid var(--default-border-dashed) !important; - background-color: var(--art-color); - } + /* 引用 */ + .w-e-text-container [data-slate-editor] blockquote { + border-left: 4px solid var(--default-border-dashed) !important; + background-color: var(--art-color); + } - .editor-wrapper { - .w-e-text-container [data-slate-editor] .table-container th:last-of-type { - border-right: 1px solid var(--default-border-dashed) !important; - } + .editor-wrapper { + .w-e-text-container [data-slate-editor] .table-container th:last-of-type { + border-right: 1px solid var(--default-border-dashed) !important; + } - .w-e-modal { - background-color: var(--art-color); - } - } + .w-e-modal { + background-color: var(--art-color); + } + } } diff --git a/src/assets/styles/core/el-light.scss b/src/assets/styles/core/el-light.scss index ddf2bc5..73a5117 100644 --- a/src/assets/styles/core/el-light.scss +++ b/src/assets/styles/core/el-light.scss @@ -2,33 +2,33 @@ // 自定义Element 亮色主题 @forward 'element-plus/theme-chalk/src/common/var.scss' with ( - $colors: ( - 'white': #ffffff, - 'black': #000000, - 'success': ( - 'base': #13deb9 - ), - 'warning': ( - 'base': #ffae1f - ), - 'danger': ( - 'base': #ff4d4f - ), - 'error': ( - 'base': #fa896b - ) - ), - $button: ( - 'hover-bg-color': var(--el-color-primary-light-9), - 'hover-border-color': var(--el-color-primary), - 'border-color': var(--el-color-primary), - 'text-color': var(--el-color-primary) - ), - $messagebox: ( - 'border-radius': '12px' - ), - $popover: ( - 'padding': '14px', - 'border-radius': '10px' - ) + $colors: ( + 'white': #ffffff, + 'black': #000000, + 'success': ( + 'base': #13deb9 + ), + 'warning': ( + 'base': #ffae1f + ), + 'danger': ( + 'base': #ff4d4f + ), + 'error': ( + 'base': #fa896b + ) + ), + $button: ( + 'hover-bg-color': var(--el-color-primary-light-9), + 'hover-border-color': var(--el-color-primary), + 'border-color': var(--el-color-primary), + 'text-color': var(--el-color-primary) + ), + $messagebox: ( + 'border-radius': '12px' + ), + $popover: ( + 'padding': '14px', + 'border-radius': '10px' + ) ); diff --git a/src/assets/styles/core/el-ui.scss b/src/assets/styles/core/el-ui.scss index 44429a1..cb50356 100644 --- a/src/assets/styles/core/el-ui.scss +++ b/src/assets/styles/core/el-ui.scss @@ -1,519 +1,519 @@ // 优化 Element Plus 组件库默认样式 :root { - // 系统主色 - --main-color: var(--el-color-primary); - --el-color-white: white !important; - --el-color-black: white !important; - // 输入框边框颜色 - // --el-border-color: #E4E4E7 !important; // DCDFE6 - // 按钮粗度 - --el-font-weight-primary: 400 !important; + // 系统主色 + --main-color: var(--el-color-primary); + --el-color-white: white !important; + --el-color-black: white !important; + // 输入框边框颜色 + // --el-border-color: #E4E4E7 !important; // DCDFE6 + // 按钮粗度 + --el-font-weight-primary: 400 !important; - --el-component-custom-height: 36px !important; + --el-component-custom-height: 36px !important; - --el-component-size: var(--el-component-custom-height) !important; + --el-component-size: var(--el-component-custom-height) !important; - // 边框、按钮圆角... - --el-border-radius-base: calc(var(--custom-radius) / 3 + 2px) !important; + // 边框、按钮圆角... + --el-border-radius-base: calc(var(--custom-radius) / 3 + 2px) !important; - --el-border-radius-small: calc(var(--custom-radius) / 3 + 4px) !important; - --el-messagebox-border-radius: calc(var(--custom-radius) / 3 + 4px) !important; - --el-popover-border-radius: calc(var(--custom-radius) / 3 + 4px) !important; + --el-border-radius-small: calc(var(--custom-radius) / 3 + 4px) !important; + --el-messagebox-border-radius: calc(var(--custom-radius) / 3 + 4px) !important; + --el-popover-border-radius: calc(var(--custom-radius) / 3 + 4px) !important; - .region .el-radio-button__original-radio:checked + .el-radio-button__inner { - color: var(--theme-color); - } + .region .el-radio-button__original-radio:checked + .el-radio-button__inner { + color: var(--theme-color); + } } // 优化 el-form-item 标签高度 .el-form-item__label { - height: var(--el-component-custom-height) !important; - line-height: var(--el-component-custom-height) !important; + height: var(--el-component-custom-height) !important; + line-height: var(--el-component-custom-height) !important; } // 日期选择器 .el-date-range-picker { - --el-datepicker-inrange-bg-color: var(--art-gray-200) !important; + --el-datepicker-inrange-bg-color: var(--art-gray-200) !important; } // el-card 背景色跟系统背景色保持一致 html.dark .el-card { - --el-card-bg-color: var(--default-box-color) !important; + --el-card-bg-color: var(--default-box-color) !important; } // 修改 el-pagination 大小 .el-pagination--default { - & { - --el-pagination-button-width: 32px !important; - --el-pagination-button-height: var(--el-pagination-button-width) !important; - } + & { + --el-pagination-button-width: 32px !important; + --el-pagination-button-height: var(--el-pagination-button-width) !important; + } - @media (max-width: 1180px) { - & { - --el-pagination-button-width: 28px !important; - } - } + @media (max-width: 1180px) { + & { + --el-pagination-button-width: 28px !important; + } + } - .el-select--default .el-select__wrapper { - min-height: var(--el-pagination-button-width) !important; - } + .el-select--default .el-select__wrapper { + min-height: var(--el-pagination-button-width) !important; + } - .el-pagination__jump .el-input { - height: var(--el-pagination-button-width) !important; - } + .el-pagination__jump .el-input { + height: var(--el-pagination-button-width) !important; + } } .el-pager li { - padding: 0 10px !important; - // border: 1px solid red !important; + padding: 0 10px !important; + // border: 1px solid red !important; } // 优化菜单折叠展开动画(提升动画流畅度) .el-menu.el-menu--inline { - transition: max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1) !important; + transition: max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1) !important; } // 优化菜单 item hover 动画(提升鼠标跟手感) .el-sub-menu__title, .el-menu-item { - transition: background-color 0s !important; + transition: background-color 0s !important; } // -------------------------------- 修改 el-size=default 组件默认高度 start -------------------------------- // 修改 el-button 高度 .el-button--default { - height: var(--el-component-custom-height) !important; + height: var(--el-component-custom-height) !important; } // circle 按钮宽度优化 .el-button--default.is-circle { - width: var(--el-component-custom-height) !important; + width: var(--el-component-custom-height) !important; } // 修改 el-select 高度 .el-select--default { - .el-select__wrapper { - min-height: var(--el-component-custom-height) !important; - } + .el-select__wrapper { + min-height: var(--el-component-custom-height) !important; + } } // 修改 el-checkbox-button 高度 .el-checkbox-button--default .el-checkbox-button__inner, // 修改 el-radio-button 高度 .el-radio-button--default .el-radio-button__inner { - padding: 10px 15px !important; + padding: 10px 15px !important; } // -------------------------------- 修改 el-size=default 组件默认高度 end -------------------------------- .el-pagination.is-background .btn-next, .el-pagination.is-background .btn-prev, .el-pagination.is-background .el-pager li { - border-radius: 6px; + border-radius: 6px; } .el-popover { - min-width: 80px; - border-radius: var(--el-border-radius-small) !important; + min-width: 80px; + border-radius: var(--el-border-radius-small) !important; } .el-dialog { - border-radius: 100px !important; - border-radius: calc(var(--custom-radius) / 1.2 + 2px) !important; - overflow: hidden; + border-radius: 100px !important; + border-radius: calc(var(--custom-radius) / 1.2 + 2px) !important; + overflow: hidden; } .el-dialog__header { - .el-dialog__title { - font-size: 16px; - } + .el-dialog__title { + font-size: 16px; + } } .el-dialog__body { - padding: 25px 0 !important; - position: relative; // 为了兼容 el-pagination 样式,需要设置 relative,不然会影响 el-pagination 的样式,比如 el-pagination__jump--small 会被影响,导致 el-pagination__jump--small 按钮无法点击,详见 URL_ADDRESS.com/element-plus/element-plus/issues/5684#issuecomment-1176299275; + padding: 25px 0 !important; + position: relative; // 为了兼容 el-pagination 样式,需要设置 relative,不然会影响 el-pagination 的样式,比如 el-pagination__jump--small 会被影响,导致 el-pagination__jump--small 按钮无法点击,详见 URL_ADDRESS.com/element-plus/element-plus/issues/5684#issuecomment-1176299275; } .el-dialog.el-dialog-border { - .el-dialog__body { - // 上边框 - &::before, + .el-dialog__body { + // 上边框 + &::before, // 下边框 &::after { - content: ''; - position: absolute; - left: -16px; - width: calc(100% + 32px); - height: 1px; - background-color: var(--art-gray-300); - } + content: ''; + position: absolute; + left: -16px; + width: calc(100% + 32px); + height: 1px; + background-color: var(--art-gray-300); + } - &::before { - top: 0; - } + &::before { + top: 0; + } - &::after { - bottom: 0; - } - } + &::after { + bottom: 0; + } + } } // el-message 样式优化 .el-message { - background-color: var(--default-box-color) !important; - border: 0 !important; - box-shadow: - 0 6px 16px 0 rgba(0, 0, 0, 0.08), - 0 3px 6px -4px rgba(0, 0, 0, 0.12), - 0 9px 28px 8px rgba(0, 0, 0, 0.05) !important; + background-color: var(--default-box-color) !important; + border: 0 !important; + box-shadow: + 0 6px 16px 0 rgba(0, 0, 0, 0.08), + 0 3px 6px -4px rgba(0, 0, 0, 0.12), + 0 9px 28px 8px rgba(0, 0, 0, 0.05) !important; - p { - font-size: 13px; - } + p { + font-size: 13px; + } } // 修改 el-dropdown 样式 .el-dropdown-menu { - padding: 6px !important; - border-radius: 10px !important; - border: none !important; + padding: 6px !important; + border-radius: 10px !important; + border: none !important; - .el-dropdown-menu__item { - padding: 6px 16px !important; - border-radius: 6px !important; + .el-dropdown-menu__item { + padding: 6px 16px !important; + border-radius: 6px !important; - &:hover:not(.is-disabled) { - color: var(--art-gray-900) !important; - background-color: var(--art-el-active-color) !important; - } + &:hover:not(.is-disabled) { + color: var(--art-gray-900) !important; + background-color: var(--art-el-active-color) !important; + } - &:focus:not(.is-disabled) { - color: var(--art-gray-900) !important; - background-color: var(--art-gray-200) !important; - } - } + &:focus:not(.is-disabled) { + color: var(--art-gray-900) !important; + background-color: var(--art-gray-200) !important; + } + } } // 隐藏 select、dropdown 的三角 .el-select__popper, .el-dropdown__popper { - margin-top: -6px !important; + margin-top: -6px !important; - .el-popper__arrow { - display: none; - } + .el-popper__arrow { + display: none; + } } .el-dropdown-selfdefine:focus { - outline: none !important; + outline: none !important; } // 处理移动端组件兼容性 @media screen and (max-width: 640px) { - .el-message-box, - .el-dialog { - width: calc(100% - 24px) !important; - } + .el-message-box, + .el-dialog { + width: calc(100% - 24px) !important; + } - .el-date-picker.has-sidebar.has-time { - width: calc(100% - 24px); - left: 12px !important; - } + .el-date-picker.has-sidebar.has-time { + width: calc(100% - 24px); + left: 12px !important; + } - .el-picker-panel *[slot='sidebar'], - .el-picker-panel__sidebar { - display: none; - } + .el-picker-panel *[slot='sidebar'], + .el-picker-panel__sidebar { + display: none; + } - .el-picker-panel *[slot='sidebar'] + .el-picker-panel__body, - .el-picker-panel__sidebar + .el-picker-panel__body { - margin-left: 0; - } + .el-picker-panel *[slot='sidebar'] + .el-picker-panel__body, + .el-picker-panel__sidebar + .el-picker-panel__body { + margin-left: 0; + } } // 修改el-button样式 .el-button { - &.el-button--text { - background-color: transparent !important; - padding: 0 !important; + &.el-button--text { + background-color: transparent !important; + padding: 0 !important; - span { - margin-left: 0 !important; - } - } + span { + margin-left: 0 !important; + } + } } // 修改el-tag样式 .el-tag { - font-weight: 500; - transition: all 0s !important; + font-weight: 500; + transition: all 0s !important; - &.el-tag--default { - height: 26px !important; - } + &.el-tag--default { + height: 26px !important; + } } .el-checkbox-group { - &.el-table-filter__checkbox-group label.el-checkbox { - height: 17px !important; + &.el-table-filter__checkbox-group label.el-checkbox { + height: 17px !important; - .el-checkbox__label { - font-weight: 400 !important; - } - } + .el-checkbox__label { + font-weight: 400 !important; + } + } } .el-radio--default { - // 优化单选按钮大小 - .el-radio__input { - .el-radio__inner { - width: 16px; - height: 16px; + // 优化单选按钮大小 + .el-radio__input { + .el-radio__inner { + width: 16px; + height: 16px; - &::after { - width: 6px; - height: 6px; - } - } - } + &::after { + width: 6px; + height: 6px; + } + } + } } .el-checkbox { - .el-checkbox__inner { - border-radius: 2px !important; - } + .el-checkbox__inner { + border-radius: 2px !important; + } } // 优化复选框样式 .el-checkbox--default { - .el-checkbox__inner { - width: 16px !important; - height: 16px !important; - border-radius: 4px !important; + .el-checkbox__inner { + width: 16px !important; + height: 16px !important; + border-radius: 4px !important; - &::before { - content: ''; - height: 4px !important; - top: 5px !important; - background-color: #fff !important; - transform: scale(0.6) !important; - } - } + &::before { + content: ''; + height: 4px !important; + top: 5px !important; + background-color: #fff !important; + transform: scale(0.6) !important; + } + } - .is-checked { - .el-checkbox__inner { - &::after { - width: 3px; - height: 8px; - margin: auto; - border: 2px solid var(--el-checkbox-checked-icon-color); - border-left: 0; - border-top: 0; - transform: translate(-45%, -60%) rotate(45deg) scale(0.86) !important; - transform-origin: center; - } - } - } + .is-checked { + .el-checkbox__inner { + &::after { + width: 3px; + height: 8px; + margin: auto; + border: 2px solid var(--el-checkbox-checked-icon-color); + border-left: 0; + border-top: 0; + transform: translate(-45%, -60%) rotate(45deg) scale(0.86) !important; + transform-origin: center; + } + } + } } .el-notification .el-notification__icon { - font-size: 22px !important; + font-size: 22px !important; } // 修改 el-message-box 样式 .el-message-box__headerbtn .el-message-box__close, .el-dialog__headerbtn .el-dialog__close { - top: 7px; - right: 7px; - width: 30px; - height: 30px; - border-radius: 5px; - transition: all 0.3s; + top: 7px; + right: 7px; + width: 30px; + height: 30px; + border-radius: 5px; + transition: all 0.3s; - &:hover { - background-color: var(--art-hover-color) !important; - color: var(--art-gray-900) !important; - } + &:hover { + background-color: var(--art-hover-color) !important; + color: var(--art-gray-900) !important; + } } .el-message-box { - padding: 25px 20px !important; + padding: 25px 20px !important; } .el-message-box__title { - font-weight: 500 !important; + font-weight: 500 !important; } .el-table__column-filter-trigger i { - color: var(--theme-color) !important; - margin: -3px 0 0 2px; + color: var(--theme-color) !important; + margin: -3px 0 0 2px; } // 去除 el-dropdown 鼠标放上去出现的边框 .el-tooltip__trigger:focus-visible { - outline: unset; + outline: unset; } // ipad 表单右侧按钮优化 @media screen and (max-width: 1180px) { - .el-table-fixed-column--right { - padding-right: 0 !important; - } + .el-table-fixed-column--right { + padding-right: 0 !important; + } } .login-out-dialog { - padding: 30px 20px !important; - border-radius: 10px !important; + padding: 30px 20px !important; + border-radius: 10px !important; } // 修改 dialog 动画 .dialog-fade-enter-active { - .el-dialog:not(.is-draggable) { - animation: dialog-open 0.3s cubic-bezier(0.32, 0.14, 0.15, 0.86); + .el-dialog:not(.is-draggable) { + animation: dialog-open 0.3s cubic-bezier(0.32, 0.14, 0.15, 0.86); - // 修复 el-dialog 动画后宽度不自适应问题 - .el-select__selected-item { - display: inline-block; - } - } + // 修复 el-dialog 动画后宽度不自适应问题 + .el-select__selected-item { + display: inline-block; + } + } } .dialog-fade-leave-active { - animation: fade-out 0.2s linear; + animation: fade-out 0.2s linear; - .el-dialog:not(.is-draggable) { - animation: dialog-close 0.5s; - } + .el-dialog:not(.is-draggable) { + animation: dialog-close 0.5s; + } } @keyframes dialog-open { - 0% { - opacity: 0; - transform: scale(0.2); - } + 0% { + opacity: 0; + transform: scale(0.2); + } - 100% { - opacity: 1; - transform: scale(1); - } + 100% { + opacity: 1; + transform: scale(1); + } } @keyframes dialog-close { - 0% { - opacity: 1; - transform: scale(1); - } + 0% { + opacity: 1; + transform: scale(1); + } - 100% { - opacity: 0; - transform: scale(0.2); - } + 100% { + opacity: 0; + transform: scale(0.2); + } } // 遮罩层动画 @keyframes fade-out { - 0% { - opacity: 1; - } + 0% { + opacity: 1; + } - 100% { - opacity: 0; - } + 100% { + opacity: 0; + } } // 修改 el-select 样式 .el-select__popper:not(.el-tree-select__popper) { - .el-select-dropdown__list { - padding: 5px !important; + .el-select-dropdown__list { + padding: 5px !important; - .el-select-dropdown__item { - height: 34px !important; - line-height: 34px !important; - border-radius: 6px !important; + .el-select-dropdown__item { + height: 34px !important; + line-height: 34px !important; + border-radius: 6px !important; - &.is-selected { - color: var(--art-gray-900) !important; - font-weight: 400 !important; - background-color: var(--art-el-active-color) !important; - margin-bottom: 4px !important; - } + &.is-selected { + color: var(--art-gray-900) !important; + font-weight: 400 !important; + background-color: var(--art-el-active-color) !important; + margin-bottom: 4px !important; + } - &:hover { - background-color: var(--art-hover-color) !important; - } - } + &:hover { + background-color: var(--art-hover-color) !important; + } + } - .el-select-dropdown__item:hover ~ .is-selected, - .el-select-dropdown__item.is-selected:has(~ .el-select-dropdown__item:hover) { - background-color: transparent !important; - } - } + .el-select-dropdown__item:hover ~ .is-selected, + .el-select-dropdown__item.is-selected:has(~ .el-select-dropdown__item:hover) { + background-color: transparent !important; + } + } } // 修改 el-tree-select 样式 .el-tree-select__popper { - .el-select-dropdown__list { - padding: 5px !important; + .el-select-dropdown__list { + padding: 5px !important; - .el-tree-node { - .el-tree-node__content { - height: 36px !important; - border-radius: 6px !important; + .el-tree-node { + .el-tree-node__content { + height: 36px !important; + border-radius: 6px !important; - &:hover { - background-color: var(--art-gray-200) !important; - } - } - } - } + &:hover { + background-color: var(--art-gray-200) !important; + } + } + } + } } // 实现水波纹在文字下面效果 .el-button > span { - position: relative; - z-index: 10; + position: relative; + z-index: 10; } // 优化颜色选择器圆角 .el-color-picker__color { - border-radius: 2px !important; + border-radius: 2px !important; } // 优化日期时间选择器底部圆角 .el-picker-panel { - .el-picker-panel__footer { - border-radius: 0 0 var(--el-border-radius-base) var(--el-border-radius-base); - } + .el-picker-panel__footer { + border-radius: 0 0 var(--el-border-radius-base) var(--el-border-radius-base); + } } // 优化树型菜单样式 .el-tree-node__content { - border-radius: 4px; - margin-bottom: 4px; - padding: 1px 0; + border-radius: 4px; + margin-bottom: 4px; + padding: 1px 0; - &:hover { - background-color: var(--art-hover-color) !important; - } + &:hover { + background-color: var(--art-hover-color) !important; + } } .dark { - .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content { - background-color: var(--art-gray-300) !important; - } + .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content { + background-color: var(--art-gray-300) !important; + } } // 隐藏折叠菜单弹窗 hover 出现的边框 .menu-left-popper:focus-within, .horizontal-menu-popper:focus-within { - box-shadow: none !important; - outline: none !important; + box-shadow: none !important; + outline: none !important; } // 数字输入组件右侧按钮高度跟随自定义组件高度 .el-input-number--default.is-controls-right { - .el-input-number__decrease, - .el-input-number__increase { - height: calc((var(--el-component-size) / 2)) !important; - } + .el-input-number__decrease, + .el-input-number__increase { + height: calc((var(--el-component-size) / 2)) !important; + } } diff --git a/src/assets/styles/core/md.scss b/src/assets/styles/core/md.scss index b22fdc2..c92fea5 100644 --- a/src/assets/styles/core/md.scss +++ b/src/assets/styles/core/md.scss @@ -8,152 +8,152 @@ $font-color: #24292e; .markdown-body h4, .markdown-body h5, .markdown-body h6 { - color: var(--art-gray-800) !important; - margin: 30px 0 10px 0; - font-weight: 600; + color: var(--art-gray-800) !important; + margin: 30px 0 10px 0; + font-weight: 600; } .markdown-body h1 { - font-size: 30px; + font-size: 30px; } @media only screen and (max-width: 550px) { - .markdown-body h1 { - font-size: 26px; - } + .markdown-body h1 { + font-size: 26px; + } - .markdown-body h2 { - font-size: 22px; - } + .markdown-body h2 { + font-size: 22px; + } - .markdown-body h3 { - font-size: 18px; - } + .markdown-body h3 { + font-size: 18px; + } } /* 块引用 */ /* ------------------------------------------------ */ .markdown-body blockquote { - color: rgba(60, 60, 67, 0.7); - font-size: 15px !important; - border-left: 0.18em solid #e7e7e8; - background: #f8f8f8; - padding: 15px 1em; - font-weight: 400 !important; + color: rgba(60, 60, 67, 0.7); + font-size: 15px !important; + border-left: 0.18em solid #e7e7e8; + background: #f8f8f8; + padding: 15px 1em; + font-weight: 400 !important; } /* 详情页文章字体颜色 */ /* ------------------------------------------------ */ .markdown-body p { - line-height: 28px; - margin-bottom: 10px; + line-height: 28px; + margin-bottom: 10px; } .markdown-body li, .markdown-body p { - color: var(--art-gray-800) !important; - font-size: 16px !important; + color: var(--art-gray-800) !important; + font-size: 16px !important; } .dark .markdown-body li span { - color: var(--art-gray-800) !important; - background-color: transparent !important; + color: var(--art-gray-800) !important; + background-color: transparent !important; } .dark .markdown-body p span { - color: var(--art-gray-800) !important; - background-color: transparent !important; + color: var(--art-gray-800) !important; + background-color: transparent !important; } .line-numbers-mode { - background-color: var(--art-code-bg); - border-radius: 8px; - position: relative; - padding-left: 32px; - box-sizing: border-box; + background-color: var(--art-code-bg); + border-radius: 8px; + position: relative; + padding-left: 32px; + box-sizing: border-box; } .line-numbers-mode pre { - flex: 1; - border-radius: 0 8px 8px 0; - background-color: var(--art-code-bg); + flex: 1; + border-radius: 0 8px 8px 0; + background-color: var(--art-code-bg); } .line-numbers-mode .line-numbers-wrapper { - width: 32px; - height: 100%; - text-align: center; - padding: 16px 0; - box-sizing: border-box; - border-right: 1px solid #000000; - position: absolute; - left: 0; - top: 0; + width: 32px; + height: 100%; + text-align: center; + padding: 16px 0; + box-sizing: border-box; + border-right: 1px solid #000000; + position: absolute; + left: 0; + top: 0; } .line-numbers-mode .line-numbers-wrapper span { - height: 23.6px; - line-height: 23.6px; - display: block; - color: #72747b; - font-size: 13px; - box-sizing: border-box; + height: 23.6px; + line-height: 23.6px; + display: block; + color: #72747b; + font-size: 13px; + box-sizing: border-box; } .line-numbers-mode .copy-btn { - display: inline-block; - display: flex; - position: absolute; - right: 10px; - top: 10px; - cursor: pointer; - opacity: 0; - background-color: #000; - border-radius: 5px; - text-align: center; - color: rgba(255, 255, 255, 0.6); - transition: opacity 0.3s; + display: inline-block; + display: flex; + position: absolute; + right: 10px; + top: 10px; + cursor: pointer; + opacity: 0; + background-color: #000; + border-radius: 5px; + text-align: center; + color: rgba(255, 255, 255, 0.6); + transition: opacity 0.3s; } .line-numbers-mode .copy-btn div { - width: 34px; - height: 34px; - line-height: 34px; - cursor: pointer; - text-align: center; - font-size: 20px; + width: 34px; + height: 34px; + line-height: 34px; + cursor: pointer; + text-align: center; + font-size: 20px; } .line-numbers-mode:hover .copy-btn { - opacity: 1; + opacity: 1; } .line-numbers-mode .copy-btn span { - height: 34px; - line-height: 34px; - font-size: 13px; - padding-left: 10px; - display: none; + height: 34px; + line-height: 34px; + font-size: 13px; + padding-left: 10px; + display: none; } .line-numbers-mode .copy-btn .show-copy { - opacity: 1; - display: block; + opacity: 1; + display: block; } .line-numbers-mode ::-webkit-scrollbar-track { - background-color: #292b30 !important; + background-color: #292b30 !important; } .markdown-body .anchor { - float: left; - line-height: 1; - margin-left: -20px; - padding-right: 4px; + float: left; + line-height: 1; + margin-left: -20px; + padding-right: 4px; } .markdown-body .anchor:focus { - outline: none; + outline: none; } .markdown-body h1 .octicon-link, @@ -162,9 +162,9 @@ $font-color: #24292e; .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link { - color: #1b1f23; - vertical-align: middle; - visibility: hidden; + color: #1b1f23; + vertical-align: middle; + visibility: hidden; } .markdown-body h1:hover .anchor, @@ -173,7 +173,7 @@ $font-color: #24292e; .markdown-body h4:hover .anchor, .markdown-body h5:hover .anchor, .markdown-body h6:hover .anchor { - text-decoration: none; + text-decoration: none; } .markdown-body h1:hover .anchor .octicon-link, @@ -182,7 +182,7 @@ $font-color: #24292e; .markdown-body h4:hover .anchor .octicon-link, .markdown-body h5:hover .anchor .octicon-link, .markdown-body h6:hover .anchor .octicon-link { - visibility: visible; + visibility: visible; } .markdown-body h1:hover .anchor .octicon-link:before, @@ -191,324 +191,324 @@ $font-color: #24292e; .markdown-body h4:hover .anchor .octicon-link:before, .markdown-body h5:hover .anchor .octicon-link:before, .markdown-body h6:hover .anchor .octicon-link:before { - width: 16px; - height: 16px; - content: ' '; - display: inline-block; + width: 16px; + height: 16px; + content: ' '; + display: inline-block; } .markdown-body { - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100%; - line-height: 1.5; - color: $font-color; - font-size: 16px; - line-height: 1.5; - word-wrap: break-word; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + line-height: 1.5; + color: $font-color; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; } .markdown-body details { - display: block; + display: block; } .markdown-body summary { - display: list-item; + display: list-item; } .markdown-body a { - background-color: initial; + background-color: initial; } .markdown-body a:active, .markdown-body a:hover { - outline-width: 0; + outline-width: 0; } .markdown-body strong { - font-weight: inherit; - font-weight: bolder; + font-weight: inherit; + font-weight: bolder; } .markdown-body p br { - display: inline; - line-height: 11px; + display: inline; + line-height: 11px; } .markdown-body img { - border-style: none; + border-style: none; } .markdown-body hr { - box-sizing: initial; - height: 0; - overflow: visible; + box-sizing: initial; + height: 0; + overflow: visible; } .markdown-body input { - font: inherit; - margin: 0; + font: inherit; + margin: 0; } .markdown-body input { - overflow: visible; + overflow: visible; } .markdown-body [type='checkbox'] { - box-sizing: border-box; - padding: 0; + box-sizing: border-box; + padding: 0; } .markdown-body * { - box-sizing: border-box; + box-sizing: border-box; } .markdown-body input { - font-size: inherit; - line-height: inherit; + font-size: inherit; + line-height: inherit; } .markdown-body a { - color: #0366d6; - text-decoration: none; + color: #0366d6; + text-decoration: none; } .markdown-body a:hover { - text-decoration: underline; + text-decoration: underline; } .markdown-body strong { - font-weight: 600; + font-weight: 600; } .markdown-body hr { - height: 0; - margin: 15px 0; - overflow: hidden; - background: transparent; - border: 0; - border-bottom: 1px solid #dfe2e5; + height: 0; + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: 1px solid #dfe2e5; } .markdown-body hr:after, .markdown-body hr:before { - display: table; - content: ''; + display: table; + content: ''; } .markdown-body hr:after { - clear: both; + clear: both; } .markdown-body table { - border-spacing: 0; - border-collapse: collapse; + border-spacing: 0; + border-collapse: collapse; } .markdown-body td, .markdown-body th { - padding: 0; + padding: 0; } .markdown-body details summary { - cursor: pointer; + cursor: pointer; } .markdown-body kbd { - display: inline-block; - padding: 3px 5px; - font: - 11px SFMono-Regular, - Consolas, - Liberation Mono, - Menlo, - monospace; - line-height: 10px; - color: #444d56; - vertical-align: middle; - background-color: #fafbfc; - border: 1px solid #d1d5da; - border-radius: 3px; - box-shadow: inset 0 -1px 0 #d1d5da; + display: inline-block; + padding: 3px 5px; + font: + 11px SFMono-Regular, + Consolas, + Liberation Mono, + Menlo, + monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #d1d5da; } .markdown-body blockquote { - margin: 0; + margin: 0; } .markdown-body ol, .markdown-body ul { - padding-left: 0; - margin-top: 0; - margin-bottom: 0; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; } .markdown-body ol ol, .markdown-body ul ol { - list-style-type: lower-roman; + list-style-type: lower-roman; } .markdown-body ol ol ol, .markdown-body ol ul ol, .markdown-body ul ol ol, .markdown-body ul ul ol { - list-style-type: lower-alpha; + list-style-type: lower-alpha; } .markdown-body dd { - margin-left: 0; + margin-left: 0; } .markdown-body code, .markdown-body pre, .markdown-body .line-number { - font-size: 14px !important; - border-radius: 8px; - background-color: #282c34; + font-size: 14px !important; + border-radius: 8px; + background-color: #282c34; } .dark { - .markdown-body code, - .markdown-body pre, - .markdown-body .line-number { - background-color: #252525; - } + .markdown-body code, + .markdown-body pre, + .markdown-body .line-number { + background-color: #252525; + } } .markdown-body pre { - margin-top: 0; - margin-bottom: 0; + margin-top: 0; + margin-bottom: 0; } .markdown-body input::-webkit-inner-spin-button, .markdown-body input::-webkit-outer-spin-button { - margin: 0; - -webkit-appearance: none; - appearance: none; + margin: 0; + -webkit-appearance: none; + appearance: none; } .markdown-body :checked + .radio-label { - position: relative; - z-index: 1; - border-color: #0366d6; + position: relative; + z-index: 1; + border-color: #0366d6; } .markdown-body .border { - border: 1px solid #e1e4e8 !important; + border: 1px solid #e1e4e8 !important; } .markdown-body .border-0 { - border: 0 !important; + border: 0 !important; } .markdown-body .border-bottom { - border-bottom: 1px solid #e1e4e8 !important; + border-bottom: 1px solid #e1e4e8 !important; } .markdown-body .rounded-1 { - border-radius: 3px !important; + border-radius: 3px !important; } .markdown-body .bg-white { - background-color: #fff !important; + background-color: #fff !important; } .markdown-body .bg-gray-light { - background-color: #fafbfc !important; + background-color: #fafbfc !important; } .markdown-body .text-gray-light { - color: #6a737d !important; + color: #6a737d !important; } .markdown-body .mb-0 { - margin-bottom: 0 !important; + margin-bottom: 0 !important; } .markdown-body .my-2 { - margin-top: 8px !important; - margin-bottom: 8px !important; + margin-top: 8px !important; + margin-bottom: 8px !important; } .markdown-body .pl-0 { - padding-left: 0 !important; + padding-left: 0 !important; } .markdown-body .py-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; } .markdown-body .pl-1 { - padding-left: 4px !important; + padding-left: 4px !important; } .markdown-body .pl-2 { - padding-left: 8px !important; + padding-left: 8px !important; } .markdown-body .py-2 { - padding-top: 8px !important; - padding-bottom: 8px !important; + padding-top: 8px !important; + padding-bottom: 8px !important; } .markdown-body .pl-3, .markdown-body .px-3 { - padding-left: 16px !important; + padding-left: 16px !important; } .markdown-body .px-3 { - padding-right: 16px !important; + padding-right: 16px !important; } .markdown-body .pl-4 { - padding-left: 24px !important; + padding-left: 24px !important; } .markdown-body .pl-5 { - padding-left: 32px !important; + padding-left: 32px !important; } .markdown-body .pl-6 { - padding-left: 40px !important; + padding-left: 40px !important; } .markdown-body .f6 { - font-size: 12px !important; + font-size: 12px !important; } .markdown-body .lh-condensed { - line-height: 1.25 !important; + line-height: 1.25 !important; } .markdown-body .text-bold { - font-weight: 600 !important; + font-weight: 600 !important; } .markdown-body .pl-c { - color: #6a737d; + color: #6a737d; } .markdown-body .pl-c1, .markdown-body .pl-s .pl-v { - color: #005cc5; + color: #005cc5; } .markdown-body .pl-e, .markdown-body .pl-en { - color: #6f42c1; + color: #6f42c1; } .markdown-body .pl-s .pl-s1, .markdown-body .pl-smi { - color: $font-color; + color: $font-color; } .markdown-body .pl-ent { - color: #22863a; + color: #22863a; } .markdown-body .pl-k { - color: #d73a49; + color: #d73a49; } .markdown-body .pl-pds, @@ -518,213 +518,213 @@ $font-color: #24292e; .markdown-body .pl-sr .pl-cce, .markdown-body .pl-sr .pl-sra, .markdown-body .pl-sr .pl-sre { - color: #032f62; + color: #032f62; } .markdown-body .pl-smw, .markdown-body .pl-v { - color: #e36209; + color: #e36209; } .markdown-body .pl-bu { - color: #b31d28; + color: #b31d28; } .markdown-body .pl-ii { - color: #fafbfc; - background-color: #b31d28; + color: #fafbfc; + background-color: #b31d28; } .markdown-body .pl-c2 { - color: #fafbfc; - background-color: #d73a49; + color: #fafbfc; + background-color: #d73a49; } .markdown-body .pl-c2:before { - content: '^M'; + content: '^M'; } .markdown-body .pl-sr .pl-cce { - font-weight: 700; - color: #22863a; + font-weight: 700; + color: #22863a; } .markdown-body .pl-ml { - color: #735c0f; + color: #735c0f; } .markdown-body .pl-mh, .markdown-body .pl-mh .pl-en, .markdown-body .pl-ms { - font-weight: 700; - color: #005cc5; + font-weight: 700; + color: #005cc5; } .markdown-body .pl-mi { - font-style: italic; - color: $font-color; + font-style: italic; + color: $font-color; } .markdown-body .pl-mb { - font-weight: 700; - color: $font-color; + font-weight: 700; + color: $font-color; } .markdown-body .pl-md { - color: #b31d28; - background-color: #ffeef0; + color: #b31d28; + background-color: #ffeef0; } .markdown-body .pl-mi1 { - color: #22863a; - background-color: #f0fff4; + color: #22863a; + background-color: #f0fff4; } .markdown-body .pl-mc { - color: #e36209; - background-color: #ffebda; + color: #e36209; + background-color: #ffebda; } .markdown-body .pl-mi2 { - color: #f6f8fa; - background-color: #005cc5; + color: #f6f8fa; + background-color: #005cc5; } .markdown-body .pl-mdr { - font-weight: 700; - color: #6f42c1; + font-weight: 700; + color: #6f42c1; } .markdown-body .pl-ba { - color: #586069; + color: #586069; } .markdown-body .pl-sg { - color: #959da5; + color: #959da5; } .markdown-body .pl-corl { - text-decoration: underline; - color: #032f62; + text-decoration: underline; + color: #032f62; } .markdown-body .mb-0 { - margin-bottom: 0 !important; + margin-bottom: 0 !important; } .markdown-body .my-2 { - margin-bottom: 8px !important; + margin-bottom: 8px !important; } .markdown-body .my-2 { - margin-top: 8px !important; + margin-top: 8px !important; } .markdown-body .pl-0 { - padding-left: 0 !important; + padding-left: 0 !important; } .markdown-body .py-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; } .markdown-body .pl-1 { - padding-left: 4px !important; + padding-left: 4px !important; } .markdown-body .pl-2 { - padding-left: 8px !important; + padding-left: 8px !important; } .markdown-body .py-2 { - padding-top: 8px !important; - padding-bottom: 8px !important; + padding-top: 8px !important; + padding-bottom: 8px !important; } .markdown-body .pl-3 { - padding-left: 16px !important; + padding-left: 16px !important; } .markdown-body .pl-4 { - padding-left: 24px !important; + padding-left: 24px !important; } .markdown-body .pl-5 { - padding-left: 32px !important; + padding-left: 32px !important; } .markdown-body .pl-6 { - padding-left: 40px !important; + padding-left: 40px !important; } .markdown-body .pl-7 { - padding-left: 48px !important; + padding-left: 48px !important; } .markdown-body .pl-8 { - padding-left: 64px !important; + padding-left: 64px !important; } .markdown-body .pl-9 { - padding-left: 80px !important; + padding-left: 80px !important; } .markdown-body .pl-10 { - padding-left: 96px !important; + padding-left: 96px !important; } .markdown-body .pl-11 { - padding-left: 112px !important; + padding-left: 112px !important; } .markdown-body .pl-12 { - padding-left: 128px !important; + padding-left: 128px !important; } .markdown-body hr { - border-bottom-color: #eee; + border-bottom-color: #eee; } .markdown-body kbd { - display: inline-block; - padding: 3px 5px; - font: - 11px SFMono-Regular, - Consolas, - Liberation Mono, - Menlo, - monospace; - line-height: 10px; - color: #444d56; - vertical-align: middle; - background-color: #fafbfc; - border: 1px solid #d1d5da; - border-radius: 3px; - box-shadow: inset 0 -1px 0 #d1d5da; + display: inline-block; + padding: 3px 5px; + font: + 11px SFMono-Regular, + Consolas, + Liberation Mono, + Menlo, + monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #d1d5da; } .markdown-body:after, .markdown-body:before { - display: table; - content: ''; + display: table; + content: ''; } .markdown-body:after { - clear: both; + clear: both; } .markdown-body > :first-child { - margin-top: 0 !important; + margin-top: 0 !important; } .markdown-body > :last-child { - margin-bottom: 0 !important; + margin-bottom: 0 !important; } .markdown-body a:not([href]) { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } .markdown-body blockquote, @@ -734,303 +734,303 @@ $font-color: #24292e; .markdown-body pre, .markdown-body table, .markdown-body ul { - margin-top: 0; - margin-bottom: 16px; + margin-top: 0; + margin-bottom: 16px; } .markdown-body hr { - height: 0.25em; - padding: 0; - margin: 24px 0; - background-color: #e1e4e8; - border: 0; + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; } .markdown-body blockquote > :first-child { - margin-top: 0; + margin-top: 0; } .markdown-body blockquote > :last-child { - margin-bottom: 0; + margin-bottom: 0; } .markdown-body ol, .markdown-body ul { - padding-left: 1em; + padding-left: 1em; } .markdown-body ol ol, .markdown-body ol ul, .markdown-body ul ol, .markdown-body ul ul { - margin-top: 0; - margin-bottom: 0; + margin-top: 0; + margin-bottom: 0; } .markdown-body li { - line-height: 28px; - font-size: 14px; - word-wrap: break-all; - list-style: disc; - margin-left: 10px; + line-height: 28px; + font-size: 14px; + word-wrap: break-all; + list-style: disc; + margin-left: 10px; } .markdown-body li > p { - margin-top: 16px; + margin-top: 16px; } .markdown-body li + li { - margin-top: 0.25em; + margin-top: 0.25em; } .markdown-body dl { - padding: 0; + padding: 0; } .markdown-body dl dt { - padding: 0; - margin-top: 16px; - font-size: 1em; - font-style: italic; - font-weight: 600; + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; } .markdown-body dl dd { - padding: 0 16px; - margin-bottom: 16px; + padding: 0 16px; + margin-bottom: 16px; } .markdown-body table { - display: block; - width: 100%; - overflow: auto; + display: block; + width: 100%; + overflow: auto; } .markdown-body table th { - font-weight: 600; + font-weight: 600; } .markdown-body table td, .markdown-body table th { - padding: 6px 13px; - border: 1px solid #dfe2e5; + padding: 6px 13px; + border: 1px solid #dfe2e5; } .markdown-body table tr { - background-color: #fff; - border-top: 1px solid #c6cbd1; + background-color: #fff; + border-top: 1px solid #c6cbd1; } .markdown-body table tr:nth-child(2n) { - background-color: #f6f8fa; + background-color: #f6f8fa; } .markdown-body img { - max-width: 100%; - box-sizing: initial; - background-color: #fff; - border: 1px solid #eee; - border: 1px solid var(--art-c-border-2); - cursor: zoom-in; + max-width: 100%; + box-sizing: initial; + background-color: #fff; + border: 1px solid #eee; + border: 1px solid var(--art-c-border-2); + cursor: zoom-in; } .markdown-body img[align='right'] { - padding-left: 20px; + padding-left: 20px; } .markdown-body img[align='left'] { - padding-right: 20px; + padding-right: 20px; } .markdown-body code { - padding: 0.2em 0.4em; - margin: 0; - font-size: 85%; - background-color: rgba(27, 31, 35, 0.05); - border-radius: 3px; + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; } .markdown-body pre { - word-wrap: normal; + word-wrap: normal; } .markdown-body pre > code { - padding: 0; - margin: 0; - font-size: 100%; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; } .markdown-body .highlight { - margin-bottom: 16px; + margin-bottom: 16px; } .markdown-body .highlight pre { - margin-bottom: 0; - word-break: normal; + margin-bottom: 0; + word-break: normal; } .markdown-body .highlight pre, .markdown-body pre { - padding: 15px 20px 15px 0; - overflow: auto; - font-size: 92%; - line-height: 1.6; + padding: 15px 20px 15px 0; + overflow: auto; + font-size: 92%; + line-height: 1.6; } .markdown-body pre code { - display: inline; - max-width: auto; - padding: 0; - margin: 0; - overflow: visible; - line-height: inherit; - word-wrap: normal; - background-color: initial; - border: 0; + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: initial; + border: 0; } .markdown-body .commit-tease-sha { - display: inline-block; - font-size: 90%; - color: #444d56; + display: inline-block; + font-size: 90%; + color: #444d56; } .markdown-body .full-commit .btn-outline:not(:disabled):hover { - color: #005cc5; - border-color: #005cc5; + color: #005cc5; + border-color: #005cc5; } .markdown-body .blob-wrapper { - overflow-x: auto; - overflow-y: hidden; + overflow-x: auto; + overflow-y: hidden; } .markdown-body .blob-wrapper-embedded { - max-height: 240px; - overflow-y: auto; + max-height: 240px; + overflow-y: auto; } .markdown-body .blob-num { - width: 1%; - min-width: 50px; - padding-right: 10px; - padding-left: 10px; - font-size: 12px; - line-height: 20px; - color: rgba(27, 31, 35, 0.3); - text-align: right; - white-space: nowrap; - vertical-align: top; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + width: 1%; + min-width: 50px; + padding-right: 10px; + padding-left: 10px; + font-size: 12px; + line-height: 20px; + color: rgba(27, 31, 35, 0.3); + text-align: right; + white-space: nowrap; + vertical-align: top; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .markdown-body .blob-num:hover { - color: rgba(27, 31, 35, 0.6); + color: rgba(27, 31, 35, 0.6); } .markdown-body .blob-num:before { - content: attr(data-line-number); + content: attr(data-line-number); } .markdown-body .blob-code { - position: relative; - padding-right: 10px; - padding-left: 10px; - line-height: 20px; - vertical-align: top; + position: relative; + padding-right: 10px; + padding-left: 10px; + line-height: 20px; + vertical-align: top; } .markdown-body .blob-code-inner { - overflow: visible; - font-size: 12px; - color: $font-color; - word-wrap: normal; - white-space: pre; + overflow: visible; + font-size: 12px; + color: $font-color; + word-wrap: normal; + white-space: pre; } .markdown-body .pl-token.active, .markdown-body .pl-token:hover { - cursor: pointer; - background: #ffea7f; + cursor: pointer; + background: #ffea7f; } .markdown-body .tab-size[data-tab-size='1'] { - -moz-tab-size: 1; - tab-size: 1; + -moz-tab-size: 1; + tab-size: 1; } .markdown-body .tab-size[data-tab-size='2'] { - -moz-tab-size: 2; - tab-size: 2; + -moz-tab-size: 2; + tab-size: 2; } .markdown-body .tab-size[data-tab-size='3'] { - -moz-tab-size: 3; - tab-size: 3; + -moz-tab-size: 3; + tab-size: 3; } .markdown-body .tab-size[data-tab-size='4'] { - -moz-tab-size: 4; - tab-size: 4; + -moz-tab-size: 4; + tab-size: 4; } .markdown-body .tab-size[data-tab-size='5'] { - -moz-tab-size: 5; - tab-size: 5; + -moz-tab-size: 5; + tab-size: 5; } .markdown-body .tab-size[data-tab-size='6'] { - -moz-tab-size: 6; - tab-size: 6; + -moz-tab-size: 6; + tab-size: 6; } .markdown-body .tab-size[data-tab-size='7'] { - -moz-tab-size: 7; - tab-size: 7; + -moz-tab-size: 7; + tab-size: 7; } .markdown-body .tab-size[data-tab-size='8'] { - -moz-tab-size: 8; - tab-size: 8; + -moz-tab-size: 8; + tab-size: 8; } .markdown-body .tab-size[data-tab-size='9'] { - -moz-tab-size: 9; - tab-size: 9; + -moz-tab-size: 9; + tab-size: 9; } .markdown-body .tab-size[data-tab-size='10'] { - -moz-tab-size: 10; - tab-size: 10; + -moz-tab-size: 10; + tab-size: 10; } .markdown-body .tab-size[data-tab-size='11'] { - -moz-tab-size: 11; - tab-size: 11; + -moz-tab-size: 11; + tab-size: 11; } .markdown-body .tab-size[data-tab-size='12'] { - -moz-tab-size: 12; - tab-size: 12; + -moz-tab-size: 12; + tab-size: 12; } .markdown-body .task-list-item { - list-style-type: none; + list-style-type: none; } .markdown-body .task-list-item + .task-list-item { - margin-top: 3px; + margin-top: 3px; } .markdown-body .task-list-item input { - margin: 0 0.2em 0.25em -1.6em; - vertical-align: middle; + margin: 0 0.2em 0.25em -1.6em; + vertical-align: middle; } diff --git a/src/assets/styles/core/mixin.scss b/src/assets/styles/core/mixin.scss index db36888..dfd6bc2 100644 --- a/src/assets/styles/core/mixin.scss +++ b/src/assets/styles/core/mixin.scss @@ -5,18 +5,18 @@ * @param {Number} 行数 */ @mixin ellipsis($rowCount: 1) { - @if $rowCount <=1 { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } @else { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: $rowCount; - -webkit-box-orient: vertical; - } + @if $rowCount <=1 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } @else { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: $rowCount; + -webkit-box-orient: vertical; + } } /** @@ -24,20 +24,20 @@ * @param {String} 类型 */ @mixin userSelect($value: none) { - user-select: $value; - -moz-user-select: $value; - -ms-user-select: $value; - -webkit-user-select: $value; + user-select: $value; + -moz-user-select: $value; + -ms-user-select: $value; + -webkit-user-select: $value; } // 绝对定位居中 @mixin absoluteCenter() { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; } /** @@ -45,113 +45,114 @@ * */ @mixin animation( - $from: ( - width: 0px - ), - $to: ( - width: 100px - ), - $name: mymove, - $animate: mymove 2s 1 linear infinite + $from: ( + width: 0px + ), + $to: ( + width: 100px + ), + $name: mymove, + $animate: mymove 2s 1 linear infinite ) { - -webkit-animation: $animate; - -o-animation: $animate; - animation: $animate; + -webkit-animation: $animate; + -o-animation: $animate; + animation: $animate; - @keyframes #{$name} { - from { - @each $key, $value in $from { - #{$key}: #{$value}; - } - } + @keyframes #{$name} { + from { + @each $key, $value in $from { + #{$key}: #{$value}; + } + } - to { - @each $key, $value in $to { - #{$key}: #{$value}; - } - } - } + to { + @each $key, $value in $to { + #{$key}: #{$value}; + } + } + } - @-webkit-keyframes #{$name} { - from { - @each $key, $value in $from { - $key: $value; - } - } + @-webkit-keyframes #{$name} { + from { + @each $key, $value in $from { + $key: $value; + } + } - to { - @each $key, $value in $to { - $key: $value; - } - } - } + to { + @each $key, $value in $to { + $key: $value; + } + } + } } // 圆形盒子 @mixin circle($size: 11px, $bg: #fff) { - border-radius: 50%; - width: $size; - height: $size; - line-height: $size; - text-align: center; - background: $bg; + border-radius: 50%; + width: $size; + height: $size; + line-height: $size; + text-align: center; + background: $bg; } // placeholder @mixin placeholder($color: #bbb) { - // Firefox - &::-moz-placeholder { - color: $color; - opacity: 1; - } + // Firefox + &::-moz-placeholder { + color: $color; + opacity: 1; + } - // Internet Explorer 10+ - &:-ms-input-placeholder { - color: $color; - } + // Internet Explorer 10+ + &:-ms-input-placeholder { + color: $color; + } - // Safari and Chrome - &::-webkit-input-placeholder { - color: $color; - } + // Safari and Chrome + &::-webkit-input-placeholder { + color: $color; + } - &:placeholder-shown { - text-overflow: ellipsis; - } + &:placeholder-shown { + text-overflow: ellipsis; + } } //背景透明,文字不透明。兼容IE8 @mixin betterTransparentize($color, $alpha) { - $c: rgba($color, $alpha); - $ie_c: ie_hex_str($c); - background: rgba($color, 1); - background: $c; - background: transparent \9; - zoom: 1; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c}); - -ms-filter: 'progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c})'; + $c: rgba($color, $alpha); + $ie_c: ie_hex_str($c); + background: rgba($color, 1); + background: $c; + background: transparent \9; + zoom: 1; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c}); + -ms-filter: 'progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c})'; } //添加浏览器前缀 @mixin browserPrefix($propertyName, $value) { - @each $prefix in -webkit-, -moz-, -ms-, -o-, '' { - #{$prefix}#{$propertyName}: $value; - } + @each $prefix in -webkit-, -moz-, -ms-, -o-, '' { + #{$prefix}#{$propertyName}: $value; + } } // 边框 @mixin border($color: red) { - border: 1px solid $color; + border: 1px solid $color; } // 背景滤镜 @mixin backdropBlur() { - --tw-backdrop-blur: blur(30px); - -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) - var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) - var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) - var(--tw-backdrop-sepia); - backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) - var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) - var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); + --tw-backdrop-blur: blur(30px); + -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) + var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) + var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) + var(--tw-backdrop-sepia); } diff --git a/src/assets/styles/core/reset.scss b/src/assets/styles/core/reset.scss index 17a3bcf..b293567 100644 --- a/src/assets/styles/core/reset.scss +++ b/src/assets/styles/core/reset.scss @@ -3,39 +3,39 @@ /*滚动条*/ /*滚动条整体部分,必须要设置*/ ::-webkit-scrollbar { - width: 8px !important; - height: 0 !important; + width: 8px !important; + height: 0 !important; } /*滚动条的轨道*/ ::-webkit-scrollbar-track { - background-color: var(--art-gray-200); + background-color: var(--art-gray-200); } /*滚动条的滑块按钮*/ ::-webkit-scrollbar-thumb { - border-radius: 5px; - background-color: #cccccc !important; - transition: all 0.2s; - -webkit-transition: all 0.2s; + border-radius: 5px; + background-color: #cccccc !important; + transition: all 0.2s; + -webkit-transition: all 0.2s; } ::-webkit-scrollbar-thumb:hover { - background-color: #b0abab !important; + background-color: #b0abab !important; } /*滚动条的上下两端的按钮*/ ::-webkit-scrollbar-button { - height: 0px; - width: 0; + height: 0px; + width: 0; } .dark { - ::-webkit-scrollbar-track { - background-color: var(--default-bg-color); - } + ::-webkit-scrollbar-track { + background-color: var(--default-bg-color); + } - ::-webkit-scrollbar-thumb { - background-color: var(--art-gray-300) !important; - } + ::-webkit-scrollbar-thumb { + background-color: var(--art-gray-300) !important; + } } diff --git a/src/assets/styles/core/router-transition.scss b/src/assets/styles/core/router-transition.scss index f47c741..f96acf1 100644 --- a/src/assets/styles/core/router-transition.scss +++ b/src/assets/styles/core/router-transition.scss @@ -2,19 +2,19 @@ // === 变量区域 === $transition: ( - // 动画持续时间 - duration: 0.25s, - // 滑动动画的移动距离 - distance: 15px, - // 默认缓动函数 - easing: cubic-bezier(0.25, 0.1, 0.25, 1), - // 淡入淡出专用的缓动函数 - fade-easing: cubic-bezier(0.4, 0, 0.6, 1) + // 动画持续时间 + duration: 0.25s, + // 滑动动画的移动距离 + distance: 15px, + // 默认缓动函数 + easing: cubic-bezier(0.25, 0.1, 0.25, 1), + // 淡入淡出专用的缓动函数 + fade-easing: cubic-bezier(0.4, 0, 0.6, 1) ); // 抽取配置值函数,提高可复用性 @function transition-config($key) { - @return map.get($transition, $key); + @return map.get($transition, $key); } // 变量简写 @@ -27,78 +27,78 @@ $fade-easing: transition-config('fade-easing'); // 淡入淡出动画 .fade { - &-enter-active, - &-leave-active { - transition: opacity $duration $fade-easing; - will-change: opacity; - } + &-enter-active, + &-leave-active { + transition: opacity $duration $fade-easing; + will-change: opacity; + } - &-enter-from, - &-leave-to { - opacity: 0; - } + &-enter-from, + &-leave-to { + opacity: 0; + } - &-enter-to, - &-leave-from { - opacity: 1; - } + &-enter-to, + &-leave-from { + opacity: 1; + } } // 滑动动画通用样式 @mixin slide-transition($direction) { - $distance-x: 0; - $distance-y: 0; + $distance-x: 0; + $distance-y: 0; - @if $direction == 'left' { - $distance-x: -$distance; - } @else if $direction == 'right' { - $distance-x: $distance; - } @else if $direction == 'top' { - $distance-y: -$distance; - } @else if $direction == 'bottom' { - $distance-y: $distance; - } + @if $direction == 'left' { + $distance-x: -$distance; + } @else if $direction == 'right' { + $distance-x: $distance; + } @else if $direction == 'top' { + $distance-y: -$distance; + } @else if $direction == 'bottom' { + $distance-y: $distance; + } - &-enter-active { - transition: - opacity $duration $easing, - transform $duration $easing; - will-change: opacity, transform; - } + &-enter-active { + transition: + opacity $duration $easing, + transform $duration $easing; + will-change: opacity, transform; + } - &-leave-active { - transition: - opacity calc($duration * 0.7) $easing, - transform calc($duration * 0.7) $easing; - will-change: opacity, transform; - } + &-leave-active { + transition: + opacity calc($duration * 0.7) $easing, + transform calc($duration * 0.7) $easing; + will-change: opacity, transform; + } - &-enter-from { - opacity: 0; - transform: translate3d($distance-x, $distance-y, 0); - } + &-enter-from { + opacity: 0; + transform: translate3d($distance-x, $distance-y, 0); + } - &-enter-to { - opacity: 1; - transform: translate3d(0, 0, 0); - } + &-enter-to { + opacity: 1; + transform: translate3d(0, 0, 0); + } - &-leave-to { - opacity: 0; - transform: translate3d(-$distance-x, -$distance-y, 0); - } + &-leave-to { + opacity: 0; + transform: translate3d(-$distance-x, -$distance-y, 0); + } } // 滑动动画方向类 .slide-left { - @include slide-transition('left'); + @include slide-transition('left'); } .slide-right { - @include slide-transition('right'); + @include slide-transition('right'); } .slide-top { - @include slide-transition('top'); + @include slide-transition('top'); } .slide-bottom { - @include slide-transition('bottom'); + @include slide-transition('bottom'); } diff --git a/src/assets/styles/core/tailwind.css b/src/assets/styles/core/tailwind.css index 1a9e22c..3a33529 100644 --- a/src/assets/styles/core/tailwind.css +++ b/src/assets/styles/core/tailwind.css @@ -3,206 +3,206 @@ /* ==================== Light Mode Variables ==================== */ :root { - /* Base Colors */ - --art-color: #ffffff; - --theme-color: var(--main-color); + /* Base Colors */ + --art-color: #ffffff; + --theme-color: var(--main-color); - /* Theme Colors - OKLCH Format */ - --art-primary: oklch(0.7 0.23 260); - --art-secondary: oklch(0.72 0.19 231.6); - --art-error: oklch(0.73 0.15 25.3); - --art-info: oklch(0.58 0.03 254.1); - --art-success: oklch(0.78 0.17 166.1); - --art-warning: oklch(0.78 0.14 75.5); - --art-danger: oklch(0.68 0.22 25.3); + /* Theme Colors - OKLCH Format */ + --art-primary: oklch(0.7 0.23 260); + --art-secondary: oklch(0.72 0.19 231.6); + --art-error: oklch(0.73 0.15 25.3); + --art-info: oklch(0.58 0.03 254.1); + --art-success: oklch(0.78 0.17 166.1); + --art-warning: oklch(0.78 0.14 75.5); + --art-danger: oklch(0.68 0.22 25.3); - /* Gray Scale - Light Mode */ - --art-gray-100: #f9fafb; - --art-gray-200: #f2f4f5; - --art-gray-300: #e6eaeb; - --art-gray-400: #dbdfe1; - --art-gray-500: #949eb7; - --art-gray-600: #7987a1; - --art-gray-700: #4d5875; - --art-gray-800: #383853; - --art-gray-900: #323251; + /* Gray Scale - Light Mode */ + --art-gray-100: #f9fafb; + --art-gray-200: #f2f4f5; + --art-gray-300: #e6eaeb; + --art-gray-400: #dbdfe1; + --art-gray-500: #949eb7; + --art-gray-600: #7987a1; + --art-gray-700: #4d5875; + --art-gray-800: #383853; + --art-gray-900: #323251; - /* Border Colors */ - --art-card-border: rgba(0, 0, 0, 0.08); + /* Border Colors */ + --art-card-border: rgba(0, 0, 0, 0.08); - --default-border: #e2e8ee; - --default-border-dashed: #dbdfe9; + --default-border: #e2e8ee; + --default-border-dashed: #dbdfe9; - /* Background Colors */ - --default-bg-color: #fafbfc; - --default-box-color: #ffffff; + /* Background Colors */ + --default-bg-color: #fafbfc; + --default-box-color: #ffffff; - /* Hover Color */ - --art-hover-color: #edeff0; + /* Hover Color */ + --art-hover-color: #edeff0; - /* Active Color */ - --art-active-color: #f2f4f5; + /* Active Color */ + --art-active-color: #f2f4f5; - /* Element Component Active Color */ - --art-el-active-color: #f2f4f5; + /* Element Component Active Color */ + --art-el-active-color: #f2f4f5; } /* ==================== Dark Mode Variables ==================== */ .dark { - /* Base Colors */ - --art-color: #000000; + /* Base Colors */ + --art-color: #000000; - /* Gray Scale - Dark Mode */ - --art-gray-100: #110f0f; - --art-gray-200: #17171c; - --art-gray-300: #393946; - --art-gray-400: #505062; - --art-gray-500: #73738c; - --art-gray-600: #8f8fa3; - --art-gray-700: #ababba; - --art-gray-800: #c7c7d1; - --art-gray-900: #e3e3e8; + /* Gray Scale - Dark Mode */ + --art-gray-100: #110f0f; + --art-gray-200: #17171c; + --art-gray-300: #393946; + --art-gray-400: #505062; + --art-gray-500: #73738c; + --art-gray-600: #8f8fa3; + --art-gray-700: #ababba; + --art-gray-800: #c7c7d1; + --art-gray-900: #e3e3e8; - /* Border Colors */ - --art-card-border: rgba(255, 255, 255, 0.08); + /* Border Colors */ + --art-card-border: rgba(255, 255, 255, 0.08); - --default-border: rgba(255, 255, 255, 0.1); - --default-border-dashed: #363843; + --default-border: rgba(255, 255, 255, 0.1); + --default-border-dashed: #363843; - /* Background Colors */ - --default-bg-color: #070707; - --default-box-color: #161618; + /* Background Colors */ + --default-bg-color: #070707; + --default-box-color: #161618; - /* Hover Color */ - --art-hover-color: #252530; + /* Hover Color */ + --art-hover-color: #252530; - /* Active Color */ - --art-active-color: #202226; + /* Active Color */ + --art-active-color: #202226; - /* Element Component Active Color */ - --art-el-active-color: #2e2e38; + /* Element Component Active Color */ + --art-el-active-color: #2e2e38; } /* ==================== Tailwind Theme Configuration ==================== */ @theme { - /* Box Color (Light: white / Dark: black) */ - --color-box: var(--default-box-color); + /* Box Color (Light: white / Dark: black) */ + --color-box: var(--default-box-color); - /* System Theme Color */ - --color-theme: var(--theme-color); + /* System Theme Color */ + --color-theme: var(--theme-color); - /* Hover Color */ - --color-hover-color: var(--art-hover-color); + /* Hover Color */ + --color-hover-color: var(--art-hover-color); - /* Active Color */ - --color-active-color: var(--art-active-color); + /* Active Color */ + --color-active-color: var(--art-active-color); - /* Active Color */ - --color-el-active-color: var(--art-active-color); + /* Active Color */ + --color-el-active-color: var(--art-active-color); - /* ElementPlus Theme Colors */ - --color-primary: var(--art-primary); - --color-secondary: var(--art-secondary); - --color-error: var(--art-error); - --color-info: var(--art-info); - --color-success: var(--art-success); - --color-warning: var(--art-warning); - --color-danger: var(--art-danger); + /* ElementPlus Theme Colors */ + --color-primary: var(--art-primary); + --color-secondary: var(--art-secondary); + --color-error: var(--art-error); + --color-info: var(--art-info); + --color-success: var(--art-success); + --color-warning: var(--art-warning); + --color-danger: var(--art-danger); - /* Gray Scale Colors (Auto-adapts to dark mode) */ - --color-g-100: var(--art-gray-100); - --color-g-200: var(--art-gray-200); - --color-g-300: var(--art-gray-300); - --color-g-400: var(--art-gray-400); - --color-g-500: var(--art-gray-500); - --color-g-600: var(--art-gray-600); - --color-g-700: var(--art-gray-700); - --color-g-800: var(--art-gray-800); - --color-g-900: var(--art-gray-900); + /* Gray Scale Colors (Auto-adapts to dark mode) */ + --color-g-100: var(--art-gray-100); + --color-g-200: var(--art-gray-200); + --color-g-300: var(--art-gray-300); + --color-g-400: var(--art-gray-400); + --color-g-500: var(--art-gray-500); + --color-g-600: var(--art-gray-600); + --color-g-700: var(--art-gray-700); + --color-g-800: var(--art-gray-800); + --color-g-900: var(--art-gray-900); } /* ==================== Custom Border Radius Utilities ==================== */ @utility rounded-custom-xs { - border-radius: calc(var(--custom-radius) / 2); + border-radius: calc(var(--custom-radius) / 2); } @utility rounded-custom-sm { - border-radius: calc(var(--custom-radius) / 2 + 2px); + border-radius: calc(var(--custom-radius) / 2 + 2px); } /* ==================== Custom Utility Classes ==================== */ @layer utilities { - /* Flexbox Layout Utilities */ - .flex-c { - @apply flex items-center; - } + /* Flexbox Layout Utilities */ + .flex-c { + @apply flex items-center; + } - .flex-b { - @apply flex justify-between; - } + .flex-b { + @apply flex justify-between; + } - .flex-cc { - @apply flex items-center justify-center; - } + .flex-cc { + @apply flex items-center justify-center; + } - .flex-cb { - @apply flex items-center justify-between; - } + .flex-cb { + @apply flex items-center justify-between; + } - /* Transition Utilities */ - .tad-200 { - @apply transition-all duration-200; - } + /* Transition Utilities */ + .tad-200 { + @apply transition-all duration-200; + } - .tad-300 { - @apply transition-all duration-300; - } + .tad-300 { + @apply transition-all duration-300; + } - /* Border Utilities */ - .border-full-d { - @apply border border-[var(--default-border)]; - } + /* Border Utilities */ + .border-full-d { + @apply border border-[var(--default-border)]; + } - .border-b-d { - @apply border-b border-[var(--default-border)]; - } + .border-b-d { + @apply border-b border-[var(--default-border)]; + } - .border-t-d { - @apply border-t border-[var(--default-border)]; - } + .border-t-d { + @apply border-t border-[var(--default-border)]; + } - .border-l-d { - @apply border-l border-[var(--default-border)]; - } + .border-l-d { + @apply border-l border-[var(--default-border)]; + } - .border-r-d { - @apply border-r border-[var(--default-border)]; - } + .border-r-d { + @apply border-r border-[var(--default-border)]; + } - /* Cursor Utilities */ - .c-p { - @apply cursor-pointer; - } + /* Cursor Utilities */ + .c-p { + @apply cursor-pointer; + } } /* ==================== Custom Component Classes ==================== */ @layer components { - /* Art Card Header Component */ - .art-card-header { - @apply flex justify-between pr-6 pb-1; + /* Art Card Header Component */ + .art-card-header { + @apply flex justify-between pr-6 pb-1; - .title { - h4 { - @apply text-lg font-medium text-g-900; - } + .title { + h4 { + @apply text-lg font-medium text-g-900; + } - p { - @apply mt-1 text-sm text-g-600; + p { + @apply mt-1 text-sm text-g-600; - span { - @apply ml-2 font-medium; - } - } - } - } + span { + @apply ml-2 font-medium; + } + } + } + } } diff --git a/src/assets/styles/core/theme-animation.scss b/src/assets/styles/core/theme-animation.scss index 377b945..60167cd 100644 --- a/src/assets/styles/core/theme-animation.scss +++ b/src/assets/styles/core/theme-animation.scss @@ -4,60 +4,60 @@ $bg-animation-color-dark: #fff; $bg-animation-duration: 0.5s; html { - --bg-animation-color: $bg-animation-color-light; + --bg-animation-color: $bg-animation-color-light; - &.dark { - --bg-animation-color: $bg-animation-color-dark; - } + &.dark { + --bg-animation-color: $bg-animation-color-dark; + } - // View transition styles - &::view-transition-old(*) { - animation: none; - } + // View transition styles + &::view-transition-old(*) { + animation: none; + } - &::view-transition-new(*) { - animation: clip $bg-animation-duration ease-in both; - } + &::view-transition-new(*) { + animation: clip $bg-animation-duration ease-in both; + } - &::view-transition-old(root) { - z-index: 1; - } + &::view-transition-old(root) { + z-index: 1; + } - &::view-transition-new(root) { - z-index: 9999; - } + &::view-transition-new(root) { + z-index: 9999; + } - &.dark { - &::view-transition-old(*) { - animation: clip $bg-animation-duration ease-in reverse both; - } + &.dark { + &::view-transition-old(*) { + animation: clip $bg-animation-duration ease-in reverse both; + } - &::view-transition-new(*) { - animation: none; - } + &::view-transition-new(*) { + animation: none; + } - &::view-transition-old(root) { - z-index: 9999; - } + &::view-transition-old(root) { + z-index: 9999; + } - &::view-transition-new(root) { - z-index: 1; - } - } + &::view-transition-new(root) { + z-index: 1; + } + } } // 定义动画 @keyframes clip { - from { - clip-path: circle(0% at var(--x) var(--y)); - } + from { + clip-path: circle(0% at var(--x) var(--y)); + } - to { - clip-path: circle(var(--r) at var(--x) var(--y)); - } + to { + clip-path: circle(var(--r) at var(--x) var(--y)); + } } // body 相关样式 body { - background-color: var(--bg-animation-color); + background-color: var(--bg-animation-color); } diff --git a/src/assets/styles/core/theme-change.scss b/src/assets/styles/core/theme-change.scss index 5b640d2..7be858f 100644 --- a/src/assets/styles/core/theme-change.scss +++ b/src/assets/styles/core/theme-change.scss @@ -1,11 +1,11 @@ // 主题切换过渡优化,优化除视觉上的不适感 .theme-change { - * { - transition: 0s !important; - } + * { + transition: 0s !important; + } - .el-switch__core, - .el-switch__action { - transition: all 0.3s !important; - } + .el-switch__core, + .el-switch__action { + transition: all 0.3s !important; + } } diff --git a/src/assets/styles/custom/one-dark-pro.scss b/src/assets/styles/custom/one-dark-pro.scss index 36bdf63..161ef38 100644 --- a/src/assets/styles/custom/one-dark-pro.scss +++ b/src/assets/styles/custom/one-dark-pro.scss @@ -1,9 +1,9 @@ .hljs { - display: block; - overflow-x: auto; - padding: 0.5em; + display: block; + overflow-x: auto; + padding: 0.5em; - color: #a6accd; + color: #a6accd; } .hljs-string, @@ -11,18 +11,18 @@ .hljs-selector-class, .hljs-template-variable, .hljs-deletion { - color: #aed07e !important; + color: #aed07e !important; } .hljs-comment, .hljs-quote { - color: #6f747d; + color: #6f747d; } .hljs-doctag, .hljs-keyword, .hljs-formula { - color: #c792ea; + color: #c792ea; } .hljs-section, @@ -30,11 +30,11 @@ .hljs-selector-tag, .hljs-deletion, .hljs-subst { - color: #c86068; + color: #c86068; } .hljs-literal { - color: #56b6c2; + color: #56b6c2; } .hljs-string, @@ -42,33 +42,33 @@ .hljs-addition, .hljs-attribute, .hljs-meta-string { - color: #abb2bf; + color: #abb2bf; } .hljs-attribute { - color: #c792ea; + color: #c792ea; } .hljs-function { - color: #c792ea; + color: #c792ea; } .hljs-type { - color: #f07178; + color: #f07178; } .hljs-title { - color: #82aaff !important; + color: #82aaff !important; } .hljs-built_in, .hljs-class { - color: #82aaff; + color: #82aaff; } // 括号 .hljs-params { - color: #a6accd; + color: #a6accd; } .hljs-attr, @@ -78,7 +78,7 @@ .hljs-selector-attr, .hljs-selector-pseudo, .hljs-number { - color: #de7e61; + color: #de7e61; } .hljs-symbol, @@ -86,13 +86,13 @@ .hljs-link, .hljs-meta, .hljs-selector-id { - color: #61aeee; + color: #61aeee; } .hljs-strong { - font-weight: bold; + font-weight: bold; } .hljs-link { - text-decoration: underline; + text-decoration: underline; } diff --git a/src/components/core/banners/art-basic-banner/index.vue b/src/components/core/banners/art-basic-banner/index.vue index 65b47e4..0dd4303 100644 --- a/src/components/core/banners/art-basic-banner/index.vue +++ b/src/components/core/banners/art-basic-banner/index.vue @@ -1,343 +1,352 @@ diff --git a/src/components/core/banners/art-card-banner/index.vue b/src/components/core/banners/art-card-banner/index.vue index 8a5f9d4..0267547 100644 --- a/src/components/core/banners/art-card-banner/index.vue +++ b/src/components/core/banners/art-card-banner/index.vue @@ -1,114 +1,114 @@ diff --git a/src/components/core/base/art-back-to-top/index.vue b/src/components/core/base/art-back-to-top/index.vue index 6f8da61..3cf0f18 100644 --- a/src/components/core/base/art-back-to-top/index.vue +++ b/src/components/core/base/art-back-to-top/index.vue @@ -1,40 +1,40 @@ diff --git a/src/components/core/base/art-logo/index.vue b/src/components/core/base/art-logo/index.vue index 8bc8309..085e931 100644 --- a/src/components/core/base/art-logo/index.vue +++ b/src/components/core/base/art-logo/index.vue @@ -1,21 +1,21 @@ diff --git a/src/components/core/base/art-svg-icon/index.vue b/src/components/core/base/art-svg-icon/index.vue index 0bfcd0c..c16f783 100644 --- a/src/components/core/base/art-svg-icon/index.vue +++ b/src/components/core/base/art-svg-icon/index.vue @@ -1,24 +1,24 @@ diff --git a/src/components/core/cards/art-bar-chart-card/index.vue b/src/components/core/cards/art-bar-chart-card/index.vue index 6815c2b..82c6e5f 100644 --- a/src/components/core/cards/art-bar-chart-card/index.vue +++ b/src/components/core/cards/art-bar-chart-card/index.vue @@ -1,103 +1,108 @@ diff --git a/src/components/core/cards/art-data-list-card/index.vue b/src/components/core/cards/art-data-list-card/index.vue index fc43323..1f093a0 100644 --- a/src/components/core/cards/art-data-list-card/index.vue +++ b/src/components/core/cards/art-data-list-card/index.vue @@ -1,74 +1,74 @@ diff --git a/src/components/core/cards/art-donut-chart-card/index.vue b/src/components/core/cards/art-donut-chart-card/index.vue index df2dcbb..1d6ff38 100644 --- a/src/components/core/cards/art-donut-chart-card/index.vue +++ b/src/components/core/cards/art-donut-chart-card/index.vue @@ -1,124 +1,124 @@ diff --git a/src/components/core/cards/art-image-card/index.vue b/src/components/core/cards/art-image-card/index.vue index d27fe00..30c5560 100644 --- a/src/components/core/cards/art-image-card/index.vue +++ b/src/components/core/cards/art-image-card/index.vue @@ -1,89 +1,89 @@ diff --git a/src/components/core/cards/art-line-chart-card/index.vue b/src/components/core/cards/art-line-chart-card/index.vue index e58c9b2..57dd214 100644 --- a/src/components/core/cards/art-line-chart-card/index.vue +++ b/src/components/core/cards/art-line-chart-card/index.vue @@ -1,126 +1,130 @@ diff --git a/src/components/core/cards/art-progress-card/index.vue b/src/components/core/cards/art-progress-card/index.vue index 048a836..b3e72ed 100644 --- a/src/components/core/cards/art-progress-card/index.vue +++ b/src/components/core/cards/art-progress-card/index.vue @@ -1,86 +1,89 @@ diff --git a/src/components/core/cards/art-stats-card/index.vue b/src/components/core/cards/art-stats-card/index.vue index 8e0341b..42ea827 100644 --- a/src/components/core/cards/art-stats-card/index.vue +++ b/src/components/core/cards/art-stats-card/index.vue @@ -1,67 +1,71 @@ diff --git a/src/components/core/cards/art-timeline-list-card/index.vue b/src/components/core/cards/art-timeline-list-card/index.vue index fbb2c78..d0e516e 100644 --- a/src/components/core/cards/art-timeline-list-card/index.vue +++ b/src/components/core/cards/art-timeline-list-card/index.vue @@ -1,69 +1,71 @@ diff --git a/src/components/core/charts/art-bar-chart/index.vue b/src/components/core/charts/art-bar-chart/index.vue index d677196..b49f8be 100644 --- a/src/components/core/charts/art-bar-chart/index.vue +++ b/src/components/core/charts/art-bar-chart/index.vue @@ -1,203 +1,209 @@ diff --git a/src/components/core/charts/art-dual-bar-compare-chart/index.vue b/src/components/core/charts/art-dual-bar-compare-chart/index.vue index 32aa60f..230c2c5 100644 --- a/src/components/core/charts/art-dual-bar-compare-chart/index.vue +++ b/src/components/core/charts/art-dual-bar-compare-chart/index.vue @@ -1,195 +1,195 @@ diff --git a/src/components/core/charts/art-h-bar-chart/index.vue b/src/components/core/charts/art-h-bar-chart/index.vue index 2e34759..d1f1787 100644 --- a/src/components/core/charts/art-h-bar-chart/index.vue +++ b/src/components/core/charts/art-h-bar-chart/index.vue @@ -1,208 +1,214 @@ diff --git a/src/components/core/charts/art-k-line-chart/index.vue b/src/components/core/charts/art-k-line-chart/index.vue index 0061b51..4d01016 100644 --- a/src/components/core/charts/art-k-line-chart/index.vue +++ b/src/components/core/charts/art-k-line-chart/index.vue @@ -1,90 +1,91 @@ diff --git a/src/components/core/charts/art-line-chart/index.vue b/src/components/core/charts/art-line-chart/index.vue index b70c2c3..673038f 100644 --- a/src/components/core/charts/art-line-chart/index.vue +++ b/src/components/core/charts/art-line-chart/index.vue @@ -1,371 +1,377 @@ diff --git a/src/components/core/charts/art-radar-chart/index.vue b/src/components/core/charts/art-radar-chart/index.vue index e99fff6..920eca7 100644 --- a/src/components/core/charts/art-radar-chart/index.vue +++ b/src/components/core/charts/art-radar-chart/index.vue @@ -1,105 +1,108 @@ diff --git a/src/components/core/charts/art-ring-chart/index.vue b/src/components/core/charts/art-ring-chart/index.vue index 79115f7..de5ca4f 100644 --- a/src/components/core/charts/art-ring-chart/index.vue +++ b/src/components/core/charts/art-ring-chart/index.vue @@ -1,133 +1,133 @@ diff --git a/src/components/core/charts/art-scatter-chart/index.vue b/src/components/core/charts/art-scatter-chart/index.vue index 995b56a..00400bd 100644 --- a/src/components/core/charts/art-scatter-chart/index.vue +++ b/src/components/core/charts/art-scatter-chart/index.vue @@ -1,115 +1,122 @@ diff --git a/src/components/core/forms/art-button-more/index.vue b/src/components/core/forms/art-button-more/index.vue index 858d305..ce2c6cf 100644 --- a/src/components/core/forms/art-button-more/index.vue +++ b/src/components/core/forms/art-button-more/index.vue @@ -1,71 +1,74 @@ diff --git a/src/components/core/forms/art-button-table/index.vue b/src/components/core/forms/art-button-table/index.vue index c849901..867aa24 100644 --- a/src/components/core/forms/art-button-table/index.vue +++ b/src/components/core/forms/art-button-table/index.vue @@ -1,59 +1,59 @@ diff --git a/src/components/core/forms/art-drag-verify/index.vue b/src/components/core/forms/art-drag-verify/index.vue index 5306e04..51d099c 100644 --- a/src/components/core/forms/art-drag-verify/index.vue +++ b/src/components/core/forms/art-drag-verify/index.vue @@ -1,430 +1,431 @@ diff --git a/src/components/core/forms/art-excel-export/index.vue b/src/components/core/forms/art-excel-export/index.vue index 08207c2..ed4d207 100644 --- a/src/components/core/forms/art-excel-export/index.vue +++ b/src/components/core/forms/art-excel-export/index.vue @@ -1,389 +1,399 @@ diff --git a/src/components/core/forms/art-excel-import/index.vue b/src/components/core/forms/art-excel-import/index.vue index 8aa82fe..7104b99 100644 --- a/src/components/core/forms/art-excel-import/index.vue +++ b/src/components/core/forms/art-excel-import/index.vue @@ -1,62 +1,62 @@ diff --git a/src/components/core/forms/art-form/index.vue b/src/components/core/forms/art-form/index.vue index 1e76f14..40468ac 100644 --- a/src/components/core/forms/art-form/index.vue +++ b/src/components/core/forms/art-form/index.vue @@ -2,310 +2,323 @@ diff --git a/src/components/core/forms/art-search-bar/index.vue b/src/components/core/forms/art-search-bar/index.vue index b25b5bb..5d57aec 100644 --- a/src/components/core/forms/art-search-bar/index.vue +++ b/src/components/core/forms/art-search-bar/index.vue @@ -2,436 +2,455 @@ diff --git a/src/components/core/forms/art-wang-editor/index.vue b/src/components/core/forms/art-wang-editor/index.vue index cfc457e..123c715 100644 --- a/src/components/core/forms/art-wang-editor/index.vue +++ b/src/components/core/forms/art-wang-editor/index.vue @@ -1,219 +1,222 @@ diff --git a/src/components/core/forms/art-wang-editor/style.scss b/src/components/core/forms/art-wang-editor/style.scss index fd5dbca..64f09f3 100644 --- a/src/components/core/forms/art-wang-editor/style.scss +++ b/src/components/core/forms/art-wang-editor/style.scss @@ -2,209 +2,209 @@ $box-radius: calc(var(--custom-radius) / 3 + 2px); // 全屏容器 z-index 调整 .w-e-full-screen-container { - z-index: 100 !important; + z-index: 100 !important; } /* 编辑器容器 */ .editor-wrapper { - width: 100%; - height: 100%; - border: 1px solid var(--art-gray-300); - border-radius: $box-radius !important; + width: 100%; + height: 100%; + border: 1px solid var(--art-gray-300); + border-radius: $box-radius !important; - .w-e-bar { - border-radius: $box-radius $box-radius 0 0 !important; - } + .w-e-bar { + border-radius: $box-radius $box-radius 0 0 !important; + } - .menu-item { - display: flex; - flex-direction: row; - align-items: center; + .menu-item { + display: flex; + flex-direction: row; + align-items: center; - i { - margin-right: 5px; - } - } + i { + margin-right: 5px; + } + } - /* 工具栏 */ - .editor-toolbar { - border-bottom: 1px solid var(--default-border); - } + /* 工具栏 */ + .editor-toolbar { + border-bottom: 1px solid var(--default-border); + } - /* 下拉选择框配置 */ - .w-e-select-list { - min-width: 140px; - padding: 5px 10px 10px; - border: none; - border-radius: $box-radius; - } + /* 下拉选择框配置 */ + .w-e-select-list { + min-width: 140px; + padding: 5px 10px 10px; + border: none; + border-radius: $box-radius; + } - /* 下拉选择框元素配置 */ - .w-e-select-list ul li { - margin-top: 5px; - font-size: 15px !important; - border-radius: $box-radius; - } + /* 下拉选择框元素配置 */ + .w-e-select-list ul li { + margin-top: 5px; + font-size: 15px !important; + border-radius: $box-radius; + } - /* 下拉选择框 正文文字大小调整 */ - .w-e-select-list ul li:last-of-type { - font-size: 16px !important; - } + /* 下拉选择框 正文文字大小调整 */ + .w-e-select-list ul li:last-of-type { + font-size: 16px !important; + } - /* 下拉选择框 hover 样式调整 */ - .w-e-select-list ul li:hover { - background-color: var(--art-gray-200); - } + /* 下拉选择框 hover 样式调整 */ + .w-e-select-list ul li:hover { + background-color: var(--art-gray-200); + } - :root { - /* 激活颜色 */ - --w-e-toolbar-active-bg-color: var(--art-gray-200); + :root { + /* 激活颜色 */ + --w-e-toolbar-active-bg-color: var(--art-gray-200); - /* toolbar 图标和文字颜色 */ - --w-e-toolbar-color: #000; + /* toolbar 图标和文字颜色 */ + --w-e-toolbar-color: #000; - /* 表格选中时候的边框颜色 */ - --w-e-textarea-selected-border-color: #ddd; + /* 表格选中时候的边框颜色 */ + --w-e-textarea-selected-border-color: #ddd; - /* 表格头背景颜色 */ - --w-e-textarea-slight-bg-color: var(--art-gray-200); - } + /* 表格头背景颜色 */ + --w-e-textarea-slight-bg-color: var(--art-gray-200); + } - /* 工具栏按钮样式 */ - .w-e-bar-item svg { - fill: var(--art-gray-800); - } + /* 工具栏按钮样式 */ + .w-e-bar-item svg { + fill: var(--art-gray-800); + } - .w-e-bar-item button { - color: var(--art-gray-800); - border-radius: $box-radius; - } + .w-e-bar-item button { + color: var(--art-gray-800); + border-radius: $box-radius; + } - /* 工具栏 hover 按钮背景颜色 */ - .w-e-bar-item button:hover { - background-color: var(--art-gray-200); - } + /* 工具栏 hover 按钮背景颜色 */ + .w-e-bar-item button:hover { + background-color: var(--art-gray-200); + } - /* 工具栏分割线 */ - .w-e-bar-divider { - height: 20px; - margin-top: 10px; - background-color: #ccc; - } + /* 工具栏分割线 */ + .w-e-bar-divider { + height: 20px; + margin-top: 10px; + background-color: #ccc; + } - /* 工具栏菜单 */ - .w-e-bar-item-group .w-e-bar-item-menus-container { - min-width: 120px; - padding: 10px 0; - border: none; - border-radius: $box-radius; + /* 工具栏菜单 */ + .w-e-bar-item-group .w-e-bar-item-menus-container { + min-width: 120px; + padding: 10px 0; + border: none; + border-radius: $box-radius; - .w-e-bar-item { - button { - width: 100%; - margin: 0 5px; - } - } - } + .w-e-bar-item { + button { + width: 100%; + margin: 0 5px; + } + } + } - /* 代码块 */ - .w-e-text-container [data-slate-editor] pre > code { - padding: 0.6rem 1rem; - background-color: var(--art-gray-50); - border-radius: $box-radius; - } + /* 代码块 */ + .w-e-text-container [data-slate-editor] pre > code { + padding: 0.6rem 1rem; + background-color: var(--art-gray-50); + border-radius: $box-radius; + } - /* 弹出框 */ - .w-e-drop-panel { - border: 0; - border-radius: $box-radius; - } + /* 弹出框 */ + .w-e-drop-panel { + border: 0; + border-radius: $box-radius; + } - a { - color: #318ef4; - } + a { + color: #318ef4; + } - .w-e-text-container { - strong, - b { - font-weight: 500; - } + .w-e-text-container { + strong, + b { + font-weight: 500; + } - i, - em { - font-style: italic; - } - } + i, + em { + font-style: italic; + } + } - /* 表格样式优化 */ - .w-e-text-container [data-slate-editor] .table-container th { - border-right: none; - } + /* 表格样式优化 */ + .w-e-text-container [data-slate-editor] .table-container th { + border-right: none; + } - .w-e-text-container [data-slate-editor] .table-container th:last-of-type { - border-right: 1px solid #ccc !important; - } + .w-e-text-container [data-slate-editor] .table-container th:last-of-type { + border-right: 1px solid #ccc !important; + } - /* 引用 */ - .w-e-text-container [data-slate-editor] blockquote { - background-color: var(--art-gray-200); - border-left: 4px solid var(--art-gray-300); - } + /* 引用 */ + .w-e-text-container [data-slate-editor] blockquote { + background-color: var(--art-gray-200); + border-left: 4px solid var(--art-gray-300); + } - /* 输入区域弹出 bar */ - .w-e-hover-bar { - border-radius: $box-radius; - } + /* 输入区域弹出 bar */ + .w-e-hover-bar { + border-radius: $box-radius; + } - /* 超链接弹窗 */ - .w-e-modal { - border: none; - border-radius: $box-radius; - } + /* 超链接弹窗 */ + .w-e-modal { + border: none; + border-radius: $box-radius; + } - /* 图片样式调整 */ - .w-e-text-container [data-slate-editor] .w-e-selected-image-container { - overflow: inherit; + /* 图片样式调整 */ + .w-e-text-container [data-slate-editor] .w-e-selected-image-container { + overflow: inherit; - &:hover { - border: 0; - } + &:hover { + border: 0; + } - img { - border: 1px solid transparent; - transition: border 0.3s; + img { + border: 1px solid transparent; + transition: border 0.3s; - &:hover { - border: 1px solid #318ef4 !important; - } - } + &:hover { + border: 1px solid #318ef4 !important; + } + } - .w-e-image-dragger { - width: 12px; - height: 12px; - background-color: #318ef4; - border: 2px solid #fff; - border-radius: $box-radius; - } + .w-e-image-dragger { + width: 12px; + height: 12px; + background-color: #318ef4; + border: 2px solid #fff; + border-radius: $box-radius; + } - .left-top { - top: -6px; - left: -6px; - } + .left-top { + top: -6px; + left: -6px; + } - .right-top { - top: -6px; - right: -6px; - } + .right-top { + top: -6px; + right: -6px; + } - .left-bottom { - bottom: -6px; - left: -6px; - } + .left-bottom { + bottom: -6px; + left: -6px; + } - .right-bottom { - right: -6px; - bottom: -6px; - } - } + .right-bottom { + right: -6px; + bottom: -6px; + } + } } diff --git a/src/components/core/layouts/art-breadcrumb/index.vue b/src/components/core/layouts/art-breadcrumb/index.vue index 4b54859..0c6b040 100644 --- a/src/components/core/layouts/art-breadcrumb/index.vue +++ b/src/components/core/layouts/art-breadcrumb/index.vue @@ -1,142 +1,142 @@ diff --git a/src/components/core/layouts/art-chat-window/index.vue b/src/components/core/layouts/art-chat-window/index.vue index f3d9471..f5876b5 100644 --- a/src/components/core/layouts/art-chat-window/index.vue +++ b/src/components/core/layouts/art-chat-window/index.vue @@ -1,262 +1,279 @@ diff --git a/src/components/core/layouts/art-fast-enter/index.vue b/src/components/core/layouts/art-fast-enter/index.vue index fdde222..f2a7c14 100644 --- a/src/components/core/layouts/art-fast-enter/index.vue +++ b/src/components/core/layouts/art-fast-enter/index.vue @@ -1,113 +1,117 @@ diff --git a/src/components/core/layouts/art-fireworks-effect/index.vue b/src/components/core/layouts/art-fireworks-effect/index.vue index be85274..06425d8 100644 --- a/src/components/core/layouts/art-fireworks-effect/index.vue +++ b/src/components/core/layouts/art-fireworks-effect/index.vue @@ -1,633 +1,643 @@ diff --git a/src/components/core/layouts/art-global-component/index.vue b/src/components/core/layouts/art-global-component/index.vue index 6908f94..97e2e95 100644 --- a/src/components/core/layouts/art-global-component/index.vue +++ b/src/components/core/layouts/art-global-component/index.vue @@ -1,14 +1,14 @@ diff --git a/src/components/core/layouts/art-global-search/index.vue b/src/components/core/layouts/art-global-search/index.vue index a7d88df..3914981 100644 --- a/src/components/core/layouts/art-global-search/index.vue +++ b/src/components/core/layouts/art-global-search/index.vue @@ -1,417 +1,430 @@ diff --git a/src/components/core/layouts/art-header-bar/index.vue b/src/components/core/layouts/art-header-bar/index.vue index 4e3c8f9..0bf51fb 100644 --- a/src/components/core/layouts/art-header-bar/index.vue +++ b/src/components/core/layouts/art-header-bar/index.vue @@ -1,485 +1,509 @@ diff --git a/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue b/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue index c8c5832..44618d5 100644 --- a/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue +++ b/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue @@ -1,159 +1,161 @@ diff --git a/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue b/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue index edd1473..a1add75 100644 --- a/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue +++ b/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue @@ -1,110 +1,110 @@ diff --git a/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue b/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue index ff32c1e..0fb7048 100644 --- a/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue +++ b/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue @@ -1,95 +1,95 @@ diff --git a/src/components/core/layouts/art-menus/art-mixed-menu/index.vue b/src/components/core/layouts/art-menus/art-mixed-menu/index.vue index 4e98246..cbeea0b 100644 --- a/src/components/core/layouts/art-menus/art-mixed-menu/index.vue +++ b/src/components/core/layouts/art-menus/art-mixed-menu/index.vue @@ -1,234 +1,237 @@ diff --git a/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue b/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue index 39387dc..0b3d5bd 100644 --- a/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue +++ b/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue @@ -1,355 +1,362 @@ diff --git a/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss b/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss index b98011c..67aeeb9 100644 --- a/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss +++ b/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss @@ -1,253 +1,253 @@ .layout-sidebar { - display: flex; - height: 100vh; - user-select: none; - scrollbar-width: none; - border-right: 1px solid var(--art-card-border); + display: flex; + height: 100vh; + user-select: none; + scrollbar-width: none; + border-right: 1px solid var(--art-card-border); - &.no-border { - border-right: none !important; - } + &.no-border { + border-right: none !important; + } - // 自定义滚动条宽度 - :deep(.el-scrollbar__bar.is-vertical) { - width: 4px; - } + // 自定义滚动条宽度 + :deep(.el-scrollbar__bar.is-vertical) { + width: 4px; + } - :deep(.el-scrollbar__thumb) { - right: -2px; - background-color: #ccc; - border-radius: 2px; - } + :deep(.el-scrollbar__thumb) { + right: -2px; + background-color: #ccc; + border-radius: 2px; + } - .dual-menu-left { - position: relative; - width: 80px; - height: 100%; - border-right: 1px solid var(--art-card-border) !important; - transition: width 0.25s; + .dual-menu-left { + position: relative; + width: 80px; + height: 100%; + border-right: 1px solid var(--art-card-border) !important; + transition: width 0.25s; - .logo { - margin: auto; - margin-top: 12px; - margin-bottom: 3px; - cursor: pointer; - } + .logo { + margin: auto; + margin-top: 12px; + margin-bottom: 3px; + cursor: pointer; + } - ul { - li { - > div { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin: 8px; - overflow: hidden; - text-align: center; - cursor: pointer; - border-radius: 5px; + ul { + li { + > div { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin: 8px; + overflow: hidden; + text-align: center; + cursor: pointer; + border-radius: 5px; - .art-svg-icon { - display: block; - margin: 0 auto; - font-size: 20px; - } + .art-svg-icon { + display: block; + margin: 0 auto; + font-size: 20px; + } - span { - display: -webkit-box; - width: 100%; - overflow: hidden; - font-size: 12px; - text-overflow: ellipsis; - -webkit-line-clamp: 1; - line-clamp: 1; - -webkit-box-orient: vertical; - } + span { + display: -webkit-box; + width: 100%; + overflow: hidden; + font-size: 12px; + text-overflow: ellipsis; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; + } - &.is-active { - background: var(--el-color-primary-light-9); + &.is-active { + background: var(--el-color-primary-light-9); - .art-svg-icon, - span { - color: var(--theme-color) !important; - } - } - } - } - } + .art-svg-icon, + span { + color: var(--theme-color) !important; + } + } + } + } + } - .switch-btn { - position: absolute; - right: 0; - bottom: 15px; - left: 0; - margin: auto; - } - } + .switch-btn { + position: absolute; + right: 0; + bottom: 15px; + left: 0; + margin: auto; + } + } - .menu-left { - position: relative; - box-sizing: border-box; - height: 100vh; + .menu-left { + position: relative; + box-sizing: border-box; + height: 100vh; - @media only screen and (width <= 640px) { - height: 100dvh; - } + @media only screen and (width <= 640px) { + height: 100dvh; + } - .el-menu { - height: 100%; - } + .el-menu { + height: 100%; + } - &:hover { - .dual-menu-collapse-btn { - opacity: 1 !important; - } - } + &:hover { + .dual-menu-collapse-btn { + opacity: 1 !important; + } + } - .dual-menu-collapse-btn { - position: absolute; - top: 50%; - right: -11px; - z-index: 10; - width: 11px; - height: 50px; - cursor: pointer; - background-color: var(--default-box-color); - border: 1px solid var(--art-card-border); - border-radius: 0 15px 15px 0; - opacity: 0; - transition: opacity 0.2s; - transform: translateY(-50%); + .dual-menu-collapse-btn { + position: absolute; + top: 50%; + right: -11px; + z-index: 10; + width: 11px; + height: 50px; + cursor: pointer; + background-color: var(--default-box-color); + border: 1px solid var(--art-card-border); + border-radius: 0 15px 15px 0; + opacity: 0; + transition: opacity 0.2s; + transform: translateY(-50%); - &:hover { - .art-svg-icon { - color: var(--art-gray-800) !important; - } - } + &:hover { + .art-svg-icon { + color: var(--art-gray-800) !important; + } + } - .art-svg-icon { - position: absolute; - top: 0; - bottom: 0; - left: -4px; - margin: auto; - transition: all 0.3s; - } - } - } + .art-svg-icon { + position: absolute; + top: 0; + bottom: 0; + left: -4px; + margin: auto; + transition: all 0.3s; + } + } + } - .header { - position: relative; - box-sizing: border-box; - display: flex; - align-items: center; - width: 100%; - height: 60px; - overflow: hidden; - line-height: 60px; - cursor: pointer; + .header { + position: relative; + box-sizing: border-box; + display: flex; + align-items: center; + width: 100%; + height: 60px; + overflow: hidden; + line-height: 60px; + cursor: pointer; - .logo { - margin-left: 22px; - } + .logo { + margin-left: 22px; + } - p { - position: absolute; - top: 0; - bottom: 0; - left: 58px; - box-sizing: border-box; - margin-left: 10px; - font-size: 18px; + p { + position: absolute; + top: 0; + bottom: 0; + left: 58px; + box-sizing: border-box; + margin-left: 10px; + font-size: 18px; - &.is-dual-menu-name { - left: 25px; - margin: auto; - } - } - } + &.is-dual-menu-name { + left: 25px; + margin: auto; + } + } + } - .el-menu { - box-sizing: border-box; - height: calc(100vh - 60px); - overflow-y: auto; - // 防止菜单内的滚动影响整个页面滚动 - overscroll-behavior: contain; - border-right: 0; - scrollbar-width: none; - -ms-scroll-chaining: contain; + .el-menu { + box-sizing: border-box; + height: calc(100vh - 60px); + overflow-y: auto; + // 防止菜单内的滚动影响整个页面滚动 + overscroll-behavior: contain; + border-right: 0; + scrollbar-width: none; + -ms-scroll-chaining: contain; - &::-webkit-scrollbar { - width: 0 !important; - } - } + &::-webkit-scrollbar { + width: 0 !important; + } + } - .menu-model { - display: none; - } + .menu-model { + display: none; + } } @media only screen and (width <= 800px) { - .layout-sidebar { - width: 0; + .layout-sidebar { + width: 0; - .header { - height: 50px; - line-height: 50px; - } + .header { + height: 50px; + line-height: 50px; + } - .el-menu { - height: calc(100vh - 60px); - } + .el-menu { + height: calc(100vh - 60px); + } - .el-menu--collapse { - width: 0; - } + .el-menu--collapse { + width: 0; + } - // 折叠状态下的header样式 - .menu-left-close .header { - .logo { - display: none; - } + // 折叠状态下的header样式 + .menu-left-close .header { + .logo { + display: none; + } - p { - left: 16px; - font-size: 0; - opacity: 0 !important; - } - } + p { + left: 16px; + font-size: 0; + opacity: 0 !important; + } + } - .menu-model { - position: fixed; - top: 0; - left: 0; - z-index: -1; - display: block; - width: 100%; - height: 100vh; - background: rgba($color: #000, $alpha: 50%); - transition: opacity 0.2s ease-in-out; - } - } + .menu-model { + position: fixed; + top: 0; + left: 0; + z-index: -1; + display: block; + width: 100%; + height: 100vh; + background: rgba($color: #000, $alpha: 50%); + transition: opacity 0.2s ease-in-out; + } + } } @media only screen and (width <= 640px) { - .layout-sidebar { - border-right: 0 !important; - } + .layout-sidebar { + border-right: 0 !important; + } } .dark { - .layout-sidebar { - border-right: 1px solid rgb(255 255 255 / 13%); + .layout-sidebar { + border-right: 1px solid rgb(255 255 255 / 13%); - :deep(.el-scrollbar__thumb) { - background-color: #777; - } + :deep(.el-scrollbar__thumb) { + background-color: #777; + } - .dual-menu-left { - border-right: 1px solid rgb(255 255 255 / 9%) !important; - } - } + .dual-menu-left { + border-right: 1px solid rgb(255 255 255 / 9%) !important; + } + } } diff --git a/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss b/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss index 7626c42..64933fb 100644 --- a/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss +++ b/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss @@ -12,247 +12,247 @@ $popup-menu-radius: 6px; // 通用菜单项样式 @mixin menu-item-base { - width: calc(100% - 16px); - margin-left: 8px; - border-radius: 6px; + width: calc(100% - 16px); + margin-left: 8px; + border-radius: 6px; - .menu-icon { - margin-left: -7px; - } + .menu-icon { + margin-left: -7px; + } } // 通用 hover 样式 @mixin menu-hover($bg-color) { - .el-sub-menu__title:hover, - .el-menu-item:not(.is-active):hover { - background: $bg-color !important; - } + .el-sub-menu__title:hover, + .el-menu-item:not(.is-active):hover { + background: $bg-color !important; + } } // 通用选中样式 @mixin menu-active($color, $bg-color, $icon-color: var(--theme-color)) { - .el-menu-item.is-active { - color: $color !important; - background-color: $bg-color; + .el-menu-item.is-active { + color: $color !important; + background-color: $bg-color; - .menu-icon { - .art-svg-icon { - color: $icon-color !important; - } - } - } + .menu-icon { + .art-svg-icon { + color: $icon-color !important; + } + } + } } // 弹窗菜单项样式 @mixin popup-menu-item { - height: $popup-menu-height; - margin-bottom: $popup-menu-margin; - border-radius: $popup-menu-radius; + height: $popup-menu-height; + margin-bottom: $popup-menu-margin; + border-radius: $popup-menu-radius; - .menu-icon { - margin-right: 5px; - } + .menu-icon { + margin-right: 5px; + } - &:last-of-type { - margin-bottom: 0; - } + &:last-of-type { + margin-bottom: 0; + } } // 主题菜单通用样式(合并 design 和 dark 主题的共同逻辑) @mixin theme-menu-base { - .el-sub-menu__title, - .el-menu-item { - @include menu-item-base; - } + .el-sub-menu__title, + .el-menu-item { + @include menu-item-base; + } } // 弹窗菜单通用样式 @mixin popup-menu-base($hover-bg, $active-color, $active-bg) { - .el-menu--popup { - padding: $popup-menu-padding; + .el-menu--popup { + padding: $popup-menu-padding; - .el-sub-menu__title:hover, - .el-menu-item:hover { - background-color: $hover-bg !important; - border-radius: $popup-menu-radius; - } + .el-sub-menu__title:hover, + .el-menu-item:hover { + background-color: $hover-bg !important; + border-radius: $popup-menu-radius; + } - .el-menu-item { - @include popup-menu-item; + .el-menu-item { + @include popup-menu-item; - &.is-active { - color: $active-color !important; - background-color: $active-bg !important; - } - } + &.is-active { + color: $active-color !important; + background-color: $active-bg !important; + } + } - .el-sub-menu { - @include popup-menu-item; + .el-sub-menu { + @include popup-menu-item; - height: $popup-menu-height !important; + height: $popup-menu-height !important; - .el-sub-menu__title { - height: $popup-menu-height !important; - border-radius: $popup-menu-radius; - } - } - } + .el-sub-menu__title { + height: $popup-menu-height !important; + border-radius: $popup-menu-radius; + } + } + } } .layout-sidebar { - // ---------------------- Modify default style ---------------------- + // ---------------------- Modify default style ---------------------- - // 菜单折叠样式 - .menu-left-close { - .header { - .logo { - margin: 0 auto; - } - } - } + // 菜单折叠样式 + .menu-left-close { + .header { + .logo { + margin: 0 auto; + } + } + } - // 菜单图标 - .menu-icon { - margin-right: 8px; - font-size: $menu-icon-size; - } + // 菜单图标 + .menu-icon { + margin-right: 8px; + font-size: $menu-icon-size; + } - // 菜单高度 - .el-sub-menu__title, - .el-menu-item { - height: $menu-height !important; - margin-bottom: 4px; - line-height: $menu-height !important; + // 菜单高度 + .el-sub-menu__title, + .el-menu-item { + height: $menu-height !important; + margin-bottom: 4px; + line-height: $menu-height !important; - span { - font-size: $menu-font-size !important; + span { + font-size: $menu-font-size !important; - @include ellipsis(); - } - } + @include ellipsis(); + } + } - // 右侧箭头 - .el-sub-menu__icon-arrow { - width: 13px !important; - font-size: 13px !important; - } + // 右侧箭头 + .el-sub-menu__icon-arrow { + width: 13px !important; + font-size: 13px !important; + } - // 菜单折叠 - .el-menu--collapse { - .el-sub-menu.is-active { - .el-sub-menu__title { - .menu-icon { - .art-svg-icon { - // 选中菜单图标颜色 - color: var(--theme-color) !important; - } - } - } - } - } + // 菜单折叠 + .el-menu--collapse { + .el-sub-menu.is-active { + .el-sub-menu__title { + .menu-icon { + .art-svg-icon { + // 选中菜单图标颜色 + color: var(--theme-color) !important; + } + } + } + } + } - // ---------------------- Design theme menu ---------------------- - .el-menu-design { - @include theme-menu-base; - @include menu-active(var(--theme-color), var(--el-color-primary-light-9)); - @include menu-hover($hover-bg-color); + // ---------------------- Design theme menu ---------------------- + .el-menu-design { + @include theme-menu-base; + @include menu-active(var(--theme-color), var(--el-color-primary-light-9)); + @include menu-hover($hover-bg-color); - .el-sub-menu__icon-arrow { - color: var(--art-gray-600); - } - } + .el-sub-menu__icon-arrow { + color: var(--art-gray-600); + } + } - // ---------------------- Dark theme menu ---------------------- - .el-menu-dark { - @include theme-menu-base; - @include menu-active(#fff, #27282d, #fff); - @include menu-hover(#0f1015); + // ---------------------- Dark theme menu ---------------------- + .el-menu-dark { + @include theme-menu-base; + @include menu-active(#fff, #27282d, #fff); + @include menu-hover(#0f1015); - .el-sub-menu__icon-arrow { - color: var(--art-gray-400); - } - } + .el-sub-menu__icon-arrow { + color: var(--art-gray-400); + } + } - // ---------------------- Light theme menu ---------------------- - .el-menu-light { - .el-sub-menu__title, - .el-menu-item { - .menu-icon { - margin-left: 1px; - } - } + // ---------------------- Light theme menu ---------------------- + .el-menu-light { + .el-sub-menu__title, + .el-menu-item { + .menu-icon { + margin-left: 1px; + } + } - .el-menu-item.is-active { - background-color: var(--el-color-primary-light-9); + .el-menu-item.is-active { + background-color: var(--el-color-primary-light-9); - .art-svg-icon { - color: var(--theme-color) !important; - } + .art-svg-icon { + color: var(--theme-color) !important; + } - &::before { - position: absolute; - top: 0; - left: 0; - width: 4px; - height: 100%; - content: ''; - background: var(--theme-color); - } - } + &::before { + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + content: ''; + background: var(--theme-color); + } + } - @include menu-hover($hover-bg-color); + @include menu-hover($hover-bg-color); - .el-sub-menu__icon-arrow { - color: var(--art-gray-600); - } - } + .el-sub-menu__icon-arrow { + color: var(--art-gray-600); + } + } } @media only screen and (width <= 640px) { - .layout-sidebar { - .el-menu-design { - > .el-sub-menu { - margin-left: 0; - } + .layout-sidebar { + .el-menu-design { + > .el-sub-menu { + margin-left: 0; + } - .el-sub-menu { - width: 100% !important; - } - } - } + .el-sub-menu { + width: 100% !important; + } + } + } } // 菜单折叠 hover 弹窗样式(浅色主题) .el-menu--vertical, .el-menu--popup-container { - @include popup-menu-base(var(--art-gray-200), var(--art-gray-900), var(--art-gray-200)); + @include popup-menu-base(var(--art-gray-200), var(--art-gray-900), var(--art-gray-200)); } // 暗黑模式菜单样式 .dark { - .el-menu--vertical, - .el-menu--popup-container { - @include popup-menu-base(var(--art-gray-200), var(--art-gray-900), #292a2e); - } + .el-menu--vertical, + .el-menu--popup-container { + @include popup-menu-base(var(--art-gray-200), var(--art-gray-900), #292a2e); + } - .layout-sidebar { - // 图标颜色、文字颜色 - .menu-icon .art-svg-icon, - .menu-name { - color: var(--art-gray-800) !important; - } + .layout-sidebar { + // 图标颜色、文字颜色 + .menu-icon .art-svg-icon, + .menu-name { + color: var(--art-gray-800) !important; + } - // 选中的文字颜色跟图标颜色 - .el-menu-item.is-active { - span, - .menu-icon .art-svg-icon { - color: var(--theme-color) !important; - } - } + // 选中的文字颜色跟图标颜色 + .el-menu-item.is-active { + span, + .menu-icon .art-svg-icon { + color: var(--theme-color) !important; + } + } - // 右侧箭头颜色 - .el-sub-menu__icon-arrow { - color: #fff; - } - } + // 右侧箭头颜色 + .el-sub-menu__icon-arrow { + color: #fff; + } + } } diff --git a/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue b/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue index a7ac6a9..4721773 100644 --- a/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue +++ b/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue @@ -1,188 +1,191 @@ diff --git a/src/components/core/layouts/art-notification/index.vue b/src/components/core/layouts/art-notification/index.vue index a58853c..d0448c5 100644 --- a/src/components/core/layouts/art-notification/index.vue +++ b/src/components/core/layouts/art-notification/index.vue @@ -1,427 +1,432 @@ diff --git a/src/components/core/layouts/art-page-content/index.vue b/src/components/core/layouts/art-page-content/index.vue index a862df1..b19fb86 100644 --- a/src/components/core/layouts/art-page-content/index.vue +++ b/src/components/core/layouts/art-page-content/index.vue @@ -1,136 +1,136 @@ diff --git a/src/components/core/layouts/art-screen-lock/index.vue b/src/components/core/layouts/art-screen-lock/index.vue index 5bf5248..2825220 100644 --- a/src/components/core/layouts/art-screen-lock/index.vue +++ b/src/components/core/layouts/art-screen-lock/index.vue @@ -1,519 +1,530 @@ diff --git a/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts b/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts index 35e8066..76438c1 100644 --- a/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts +++ b/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts @@ -8,241 +8,241 @@ import { headerBarConfig } from '@/config/modules/headerBar' * 设置项配置选项管理 */ export function useSettingsConfig() { - const { t } = useI18n() + const { t } = useI18n() - // 标签页风格选项 - const tabStyleOptions = computed(() => [ - { - value: 'tab-default', - label: t('setting.tabStyle.default') - }, - { - value: 'tab-card', - label: t('setting.tabStyle.card') - }, - { - value: 'tab-google', - label: t('setting.tabStyle.google') - } - ]) + // 标签页风格选项 + const tabStyleOptions = computed(() => [ + { + value: 'tab-default', + label: t('setting.tabStyle.default') + }, + { + value: 'tab-card', + label: t('setting.tabStyle.card') + }, + { + value: 'tab-google', + label: t('setting.tabStyle.google') + } + ]) - // 页面切换动画选项 - const pageTransitionOptions = computed(() => [ - { - value: '', - label: t('setting.transition.list.none') - }, - { - value: 'fade', - label: t('setting.transition.list.fade') - }, - { - value: 'slide-left', - label: t('setting.transition.list.slideLeft') - }, - { - value: 'slide-bottom', - label: t('setting.transition.list.slideBottom') - }, - { - value: 'slide-top', - label: t('setting.transition.list.slideTop') - } - ]) + // 页面切换动画选项 + const pageTransitionOptions = computed(() => [ + { + value: '', + label: t('setting.transition.list.none') + }, + { + value: 'fade', + label: t('setting.transition.list.fade') + }, + { + value: 'slide-left', + label: t('setting.transition.list.slideLeft') + }, + { + value: 'slide-bottom', + label: t('setting.transition.list.slideBottom') + }, + { + value: 'slide-top', + label: t('setting.transition.list.slideTop') + } + ]) - // 圆角大小选项 - const customRadiusOptions = [ - { value: '0', label: '0' }, - { value: '0.25', label: '0.25' }, - { value: '0.5', label: '0.5' }, - { value: '0.75', label: '0.75' }, - { value: '1', label: '1' } - ] + // 圆角大小选项 + const customRadiusOptions = [ + { value: '0', label: '0' }, + { value: '0.25', label: '0.25' }, + { value: '0.5', label: '0.5' }, + { value: '0.75', label: '0.75' }, + { value: '1', label: '1' } + ] - // 容器宽度选项 - const containerWidthOptions = computed(() => [ - { - value: ContainerWidthEnum.FULL, - label: t('setting.container.list[0]'), - icon: 'icon-park-outline:auto-width' - }, - { - value: ContainerWidthEnum.BOXED, - label: t('setting.container.list[1]'), - icon: 'ix:width' - } - ]) + // 容器宽度选项 + const containerWidthOptions = computed(() => [ + { + value: ContainerWidthEnum.FULL, + label: t('setting.container.list[0]'), + icon: 'icon-park-outline:auto-width' + }, + { + value: ContainerWidthEnum.BOXED, + label: t('setting.container.list[1]'), + icon: 'ix:width' + } + ]) - // 盒子样式选项 - const boxStyleOptions = computed(() => [ - { - value: 'border-mode', - label: t('setting.box.list[0]'), - type: 'border-mode' as const - }, - { - value: 'shadow-mode', - label: t('setting.box.list[1]'), - type: 'shadow-mode' as const - } - ]) + // 盒子样式选项 + const boxStyleOptions = computed(() => [ + { + value: 'border-mode', + label: t('setting.box.list[0]'), + type: 'border-mode' as const + }, + { + value: 'shadow-mode', + label: t('setting.box.list[1]'), + type: 'shadow-mode' as const + } + ]) - // 从配置文件获取的选项 - const configOptions = { - // 主题色彩选项 - mainColors: AppConfig.systemMainColor, + // 从配置文件获取的选项 + const configOptions = { + // 主题色彩选项 + mainColors: AppConfig.systemMainColor, - // 主题风格选项 - themeList: AppConfig.settingThemeList, + // 主题风格选项 + themeList: AppConfig.settingThemeList, - // 菜单布局选项 - menuLayoutList: AppConfig.menuLayoutList - } + // 菜单布局选项 + menuLayoutList: AppConfig.menuLayoutList + } - // 基础设置项配置 - const basicSettingsConfig = computed(() => { - // 定义所有基础设置项 - const allSettings = [ - { - key: 'showWorkTab', - label: t('setting.basics.list.multiTab'), - type: 'switch' as const, - handler: 'workTab', - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'uniqueOpened', - label: t('setting.basics.list.accordion'), - type: 'switch' as const, - handler: 'uniqueOpened', - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'showMenuButton', - label: t('setting.basics.list.collapseSidebar'), - type: 'switch' as const, - handler: 'menuButton', - headerBarKey: 'menuButton' as const - }, - { - key: 'showFastEnter', - label: t('setting.basics.list.fastEnter'), - type: 'switch' as const, - handler: 'fastEnter', - headerBarKey: 'fastEnter' as const - }, - { - key: 'showRefreshButton', - label: t('setting.basics.list.reloadPage'), - type: 'switch' as const, - handler: 'refreshButton', - headerBarKey: 'refreshButton' as const - }, - { - key: 'showCrumbs', - label: t('setting.basics.list.breadcrumb'), - type: 'switch' as const, - handler: 'crumbs', - mobileHide: true, - headerBarKey: 'breadcrumb' as const - }, - { - key: 'showLanguage', - label: t('setting.basics.list.language'), - type: 'switch' as const, - handler: 'language', - headerBarKey: 'language' as const - }, - { - key: 'showNprogress', - label: t('setting.basics.list.progressBar'), - type: 'switch' as const, - handler: 'nprogress', - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'colorWeak', - label: t('setting.basics.list.weakMode'), - type: 'switch' as const, - handler: 'colorWeak', - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'watermarkVisible', - label: t('setting.basics.list.watermark'), - type: 'switch' as const, - handler: 'watermark', - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'menuOpenWidth', - label: t('setting.basics.list.menuWidth'), - type: 'input-number' as const, - handler: 'menuOpenWidth', - min: 180, - max: 320, - step: 10, - style: { width: '120px' }, - controlsPosition: 'right' as const, - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'tabStyle', - label: t('setting.basics.list.tabStyle'), - type: 'select' as const, - handler: 'tabStyle', - options: tabStyleOptions.value, - style: { width: '120px' }, - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'pageTransition', - label: t('setting.basics.list.pageTransition'), - type: 'select' as const, - handler: 'pageTransition', - options: pageTransitionOptions.value, - style: { width: '120px' }, - headerBarKey: null // 不依赖headerBar配置 - }, - { - key: 'customRadius', - label: t('setting.basics.list.borderRadius'), - type: 'select' as const, - handler: 'customRadius', - options: customRadiusOptions, - style: { width: '120px' }, - headerBarKey: null // 不依赖headerBar配置 - } - ] + // 基础设置项配置 + const basicSettingsConfig = computed(() => { + // 定义所有基础设置项 + const allSettings = [ + { + key: 'showWorkTab', + label: t('setting.basics.list.multiTab'), + type: 'switch' as const, + handler: 'workTab', + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'uniqueOpened', + label: t('setting.basics.list.accordion'), + type: 'switch' as const, + handler: 'uniqueOpened', + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'showMenuButton', + label: t('setting.basics.list.collapseSidebar'), + type: 'switch' as const, + handler: 'menuButton', + headerBarKey: 'menuButton' as const + }, + { + key: 'showFastEnter', + label: t('setting.basics.list.fastEnter'), + type: 'switch' as const, + handler: 'fastEnter', + headerBarKey: 'fastEnter' as const + }, + { + key: 'showRefreshButton', + label: t('setting.basics.list.reloadPage'), + type: 'switch' as const, + handler: 'refreshButton', + headerBarKey: 'refreshButton' as const + }, + { + key: 'showCrumbs', + label: t('setting.basics.list.breadcrumb'), + type: 'switch' as const, + handler: 'crumbs', + mobileHide: true, + headerBarKey: 'breadcrumb' as const + }, + { + key: 'showLanguage', + label: t('setting.basics.list.language'), + type: 'switch' as const, + handler: 'language', + headerBarKey: 'language' as const + }, + { + key: 'showNprogress', + label: t('setting.basics.list.progressBar'), + type: 'switch' as const, + handler: 'nprogress', + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'colorWeak', + label: t('setting.basics.list.weakMode'), + type: 'switch' as const, + handler: 'colorWeak', + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'watermarkVisible', + label: t('setting.basics.list.watermark'), + type: 'switch' as const, + handler: 'watermark', + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'menuOpenWidth', + label: t('setting.basics.list.menuWidth'), + type: 'input-number' as const, + handler: 'menuOpenWidth', + min: 180, + max: 320, + step: 10, + style: { width: '120px' }, + controlsPosition: 'right' as const, + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'tabStyle', + label: t('setting.basics.list.tabStyle'), + type: 'select' as const, + handler: 'tabStyle', + options: tabStyleOptions.value, + style: { width: '120px' }, + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'pageTransition', + label: t('setting.basics.list.pageTransition'), + type: 'select' as const, + handler: 'pageTransition', + options: pageTransitionOptions.value, + style: { width: '120px' }, + headerBarKey: null // 不依赖headerBar配置 + }, + { + key: 'customRadius', + label: t('setting.basics.list.borderRadius'), + type: 'select' as const, + handler: 'customRadius', + options: customRadiusOptions, + style: { width: '120px' }, + headerBarKey: null // 不依赖headerBar配置 + } + ] - // 根据 headerBarConfig 过滤设置项 - return ( - allSettings - .filter((setting) => { - // 如果设置项不依赖headerBar配置,则始终显示 - if (setting.headerBarKey === null) { - return true - } + // 根据 headerBarConfig 过滤设置项 + return ( + allSettings + .filter((setting) => { + // 如果设置项不依赖headerBar配置,则始终显示 + if (setting.headerBarKey === null) { + return true + } - // 如果依赖headerBar配置,检查对应的功能是否启用 - const headerBarFeature = headerBarConfig[setting.headerBarKey] - return headerBarFeature?.enabled !== false - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .map(({ headerBarKey: _headerBarKey, ...setting }) => setting) - ) - }) + // 如果依赖headerBar配置,检查对应的功能是否启用 + const headerBarFeature = headerBarConfig[setting.headerBarKey] + return headerBarFeature?.enabled !== false + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(({ headerBarKey: _headerBarKey, ...setting }) => setting) + ) + }) - return { - // 选项配置 - tabStyleOptions, - pageTransitionOptions, - customRadiusOptions, - containerWidthOptions, - boxStyleOptions, - configOptions, + return { + // 选项配置 + tabStyleOptions, + pageTransitionOptions, + customRadiusOptions, + containerWidthOptions, + boxStyleOptions, + configOptions, - // 设置项配置 - basicSettingsConfig - } + // 设置项配置 + basicSettingsConfig + } } diff --git a/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts b/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts index 392c690..be07073 100644 --- a/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts +++ b/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts @@ -6,162 +6,162 @@ import type { ContainerWidthEnum } from '@/enums/appEnum' * 设置项通用处理逻辑 */ export function useSettingsHandlers() { - const settingStore = useSettingStore() + const settingStore = useSettingStore() - // DOM 操作相关 - const domOperations = { - // 设置HTML类名 - setHtmlClass: (className: string, add: boolean) => { - const el = document.getElementsByTagName('html')[0] - if (add) { - el.classList.add(className) - } else { - el.classList.remove(className) - } - }, + // DOM 操作相关 + const domOperations = { + // 设置HTML类名 + setHtmlClass: (className: string, add: boolean) => { + const el = document.getElementsByTagName('html')[0] + if (add) { + el.classList.add(className) + } else { + el.classList.remove(className) + } + }, - // 设置根元素属性 - setRootAttribute: (attribute: string, value: string) => { - const el = document.documentElement - el.setAttribute(attribute, value) - }, + // 设置根元素属性 + setRootAttribute: (attribute: string, value: string) => { + const el = document.documentElement + el.setAttribute(attribute, value) + }, - // 设置body类名 - setBodyClass: (className: string, add: boolean) => { - const el = document.getElementsByTagName('body')[0] - if (add) { - el.classList.add(className) - } else { - el.classList.remove(className) - } - } - } + // 设置body类名 + setBodyClass: (className: string, add: boolean) => { + const el = document.getElementsByTagName('body')[0] + if (add) { + el.classList.add(className) + } else { + el.classList.remove(className) + } + } + } - // 通用切换处理器 - const createToggleHandler = (storeMethod: () => void, callback?: () => void) => { - return () => { - storeMethod() - callback?.() - } - } + // 通用切换处理器 + const createToggleHandler = (storeMethod: () => void, callback?: () => void) => { + return () => { + storeMethod() + callback?.() + } + } - // 通用值变更处理器 - const createValueHandler = ( - storeMethod: (value: T) => void, - callback?: (value: T) => void - ) => { - return (value: T) => { - if (value !== undefined && value !== null) { - storeMethod(value) - callback?.(value) - } - } - } + // 通用值变更处理器 + const createValueHandler = ( + storeMethod: (value: T) => void, + callback?: (value: T) => void + ) => { + return (value: T) => { + if (value !== undefined && value !== null) { + storeMethod(value) + callback?.(value) + } + } + } - // 基础设置处理器 - const basicHandlers = { - // 工作台标签页 - workTab: createToggleHandler(() => settingStore.setWorkTab(!settingStore.showWorkTab)), + // 基础设置处理器 + const basicHandlers = { + // 工作台标签页 + workTab: createToggleHandler(() => settingStore.setWorkTab(!settingStore.showWorkTab)), - // 菜单手风琴 - uniqueOpened: createToggleHandler(() => settingStore.setUniqueOpened()), + // 菜单手风琴 + uniqueOpened: createToggleHandler(() => settingStore.setUniqueOpened()), - // 显示菜单按钮 - menuButton: createToggleHandler(() => settingStore.setButton()), + // 显示菜单按钮 + menuButton: createToggleHandler(() => settingStore.setButton()), - // 显示快速入口 - fastEnter: createToggleHandler(() => settingStore.setFastEnter()), + // 显示快速入口 + fastEnter: createToggleHandler(() => settingStore.setFastEnter()), - // 显示刷新按钮 - refreshButton: createToggleHandler(() => settingStore.setShowRefreshButton()), + // 显示刷新按钮 + refreshButton: createToggleHandler(() => settingStore.setShowRefreshButton()), - // 显示面包屑 - crumbs: createToggleHandler(() => settingStore.setCrumbs()), + // 显示面包屑 + crumbs: createToggleHandler(() => settingStore.setCrumbs()), - // 显示语言切换 - language: createToggleHandler(() => settingStore.setLanguage()), + // 显示语言切换 + language: createToggleHandler(() => settingStore.setLanguage()), - // 显示进度条 - nprogress: createToggleHandler(() => settingStore.setNprogress()), + // 显示进度条 + nprogress: createToggleHandler(() => settingStore.setNprogress()), - // 色弱模式 - colorWeak: createToggleHandler( - () => settingStore.setColorWeak(), - () => { - domOperations.setHtmlClass('color-weak', settingStore.colorWeak) - } - ), + // 色弱模式 + colorWeak: createToggleHandler( + () => settingStore.setColorWeak(), + () => { + domOperations.setHtmlClass('color-weak', settingStore.colorWeak) + } + ), - // 水印显示 - watermark: createToggleHandler(() => - settingStore.setWatermarkVisible(!settingStore.watermarkVisible) - ), + // 水印显示 + watermark: createToggleHandler(() => + settingStore.setWatermarkVisible(!settingStore.watermarkVisible) + ), - // 菜单展开宽度 - menuOpenWidth: createValueHandler((width: number) => - settingStore.setMenuOpenWidth(width) - ), + // 菜单展开宽度 + menuOpenWidth: createValueHandler((width: number) => + settingStore.setMenuOpenWidth(width) + ), - // 标签页风格 - tabStyle: createValueHandler((style: string) => settingStore.setTabStyle(style)), + // 标签页风格 + tabStyle: createValueHandler((style: string) => settingStore.setTabStyle(style)), - // 页面切换动画 - pageTransition: createValueHandler((transition: string) => - settingStore.setPageTransition(transition) - ), + // 页面切换动画 + pageTransition: createValueHandler((transition: string) => + settingStore.setPageTransition(transition) + ), - // 圆角大小 - customRadius: createValueHandler((radius: string) => - settingStore.setCustomRadius(radius) - ) - } + // 圆角大小 + customRadius: createValueHandler((radius: string) => + settingStore.setCustomRadius(radius) + ) + } - // 盒子样式处理器 - const boxStyleHandlers = { - // 设置盒子模式 - setBoxMode: (type: 'border-mode' | 'shadow-mode') => { - const { boxBorderMode } = storeToRefs(settingStore) + // 盒子样式处理器 + const boxStyleHandlers = { + // 设置盒子模式 + setBoxMode: (type: 'border-mode' | 'shadow-mode') => { + const { boxBorderMode } = storeToRefs(settingStore) - // 防止重复设置 - if ( - (type === 'shadow-mode' && boxBorderMode.value === false) || - (type === 'border-mode' && boxBorderMode.value === true) - ) { - return - } + // 防止重复设置 + if ( + (type === 'shadow-mode' && boxBorderMode.value === false) || + (type === 'border-mode' && boxBorderMode.value === true) + ) { + return + } - setTimeout(() => { - domOperations.setRootAttribute('data-box-mode', type) - settingStore.setBorderMode() - }, 50) - } - } + setTimeout(() => { + domOperations.setRootAttribute('data-box-mode', type) + settingStore.setBorderMode() + }, 50) + } + } - // 颜色设置处理器 - const colorHandlers = { - // 选择主题色 - selectColor: (theme: string) => { - settingStore.setElementTheme(theme) - settingStore.reload() - } - } + // 颜色设置处理器 + const colorHandlers = { + // 选择主题色 + selectColor: (theme: string) => { + settingStore.setElementTheme(theme) + settingStore.reload() + } + } - // 容器设置处理器 - const containerHandlers = { - // 设置容器宽度 - setWidth: (type: ContainerWidthEnum) => { - settingStore.setContainerWidth(type) - settingStore.reload() - } - } + // 容器设置处理器 + const containerHandlers = { + // 设置容器宽度 + setWidth: (type: ContainerWidthEnum) => { + settingStore.setContainerWidth(type) + settingStore.reload() + } + } - return { - domOperations, - basicHandlers, - boxStyleHandlers, - colorHandlers, - containerHandlers, - createToggleHandler, - createValueHandler - } + return { + domOperations, + basicHandlers, + boxStyleHandlers, + colorHandlers, + containerHandlers, + createToggleHandler, + createValueHandler + } } diff --git a/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts b/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts index 358ef57..ea48aed 100644 --- a/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts +++ b/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts @@ -14,194 +14,194 @@ import { useSettingsHandlers } from './useSettingsHandlers' * 设置面板核心逻辑管理 */ export function useSettingsPanel() { - const settingStore = useSettingStore() - const { systemThemeType, systemThemeMode, menuType } = storeToRefs(settingStore) + const settingStore = useSettingStore() + const { systemThemeType, systemThemeMode, menuType } = storeToRefs(settingStore) - // Composables - const { openFestival, cleanup } = useCeremony() - const { setSystemTheme, setSystemAutoTheme } = useTheme() - const { initColorWeak } = useSettingsState() - const { domOperations } = useSettingsHandlers() + // Composables + const { openFestival, cleanup } = useCeremony() + const { setSystemTheme, setSystemAutoTheme } = useTheme() + const { initColorWeak } = useSettingsState() + const { domOperations } = useSettingsHandlers() - // 响应式状态 - const showDrawer = ref(false) + // 响应式状态 + const showDrawer = ref(false) - // 使用 VueUse breakpoints 优化性能 - const breakpoints = useBreakpoints({ tablet: 1000 }) - const isMobile = breakpoints.smaller('tablet') + // 使用 VueUse breakpoints 优化性能 + const breakpoints = useBreakpoints({ tablet: 1000 }) + const isMobile = breakpoints.smaller('tablet') - // 记录窗口宽度变化前的菜单类型 - const beforeMenuType = ref() - const hasChangedMenu = ref(false) + // 记录窗口宽度变化前的菜单类型 + const beforeMenuType = ref() + const hasChangedMenu = ref(false) - // 计算属性 - const systemThemeColor = computed(() => settingStore.systemThemeColor as string) + // 计算属性 + const systemThemeColor = computed(() => settingStore.systemThemeColor as string) - // 主题相关处理 - const useThemeHandlers = () => { - // 初始化系统颜色 - const initSystemColor = () => { - if (!AppConfig.systemMainColor.includes(systemThemeColor.value)) { - settingStore.setElementTheme(AppConfig.systemMainColor[0]) - settingStore.reload() - } - } + // 主题相关处理 + const useThemeHandlers = () => { + // 初始化系统颜色 + const initSystemColor = () => { + if (!AppConfig.systemMainColor.includes(systemThemeColor.value)) { + settingStore.setElementTheme(AppConfig.systemMainColor[0]) + settingStore.reload() + } + } - // 初始化系统主题 - const initSystemTheme = () => { - if (systemThemeMode.value === SystemThemeEnum.AUTO) { - setSystemAutoTheme() - } else { - setSystemTheme(systemThemeType.value) - } - } + // 初始化系统主题 + const initSystemTheme = () => { + if (systemThemeMode.value === SystemThemeEnum.AUTO) { + setSystemAutoTheme() + } else { + setSystemTheme(systemThemeType.value) + } + } - // 监听系统主题变化 - const listenerSystemTheme = () => { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') - mediaQuery.addEventListener('change', initSystemTheme) - return () => { - mediaQuery.removeEventListener('change', initSystemTheme) - } - } + // 监听系统主题变化 + const listenerSystemTheme = () => { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + mediaQuery.addEventListener('change', initSystemTheme) + return () => { + mediaQuery.removeEventListener('change', initSystemTheme) + } + } - return { - initSystemColor, - initSystemTheme, - listenerSystemTheme - } - } + return { + initSystemColor, + initSystemTheme, + listenerSystemTheme + } + } - // 响应式布局处理 - const useResponsiveLayout = () => { - // 使用 watch 监听断点变化,性能更优 - const stopWatch = watch( - isMobile, - (mobile: boolean) => { - if (mobile) { - // 切换到移动端布局 - if (!hasChangedMenu.value) { - beforeMenuType.value = menuType.value - useSettingsState().switchMenuLayouts(MenuTypeEnum.LEFT) - settingStore.setMenuOpen(false) - hasChangedMenu.value = true - } - } else { - // 恢复桌面端布局 - if (hasChangedMenu.value && beforeMenuType.value) { - useSettingsState().switchMenuLayouts(beforeMenuType.value) - settingStore.setMenuOpen(true) - hasChangedMenu.value = false - } - } - }, - { immediate: true } - ) + // 响应式布局处理 + const useResponsiveLayout = () => { + // 使用 watch 监听断点变化,性能更优 + const stopWatch = watch( + isMobile, + (mobile: boolean) => { + if (mobile) { + // 切换到移动端布局 + if (!hasChangedMenu.value) { + beforeMenuType.value = menuType.value + useSettingsState().switchMenuLayouts(MenuTypeEnum.LEFT) + settingStore.setMenuOpen(false) + hasChangedMenu.value = true + } + } else { + // 恢复桌面端布局 + if (hasChangedMenu.value && beforeMenuType.value) { + useSettingsState().switchMenuLayouts(beforeMenuType.value) + settingStore.setMenuOpen(true) + hasChangedMenu.value = false + } + } + }, + { immediate: true } + ) - return { stopWatch } - } + return { stopWatch } + } - // 抽屉控制 - const useDrawerControl = () => { - // 用于存储 setTimeout 的 ID,以便在需要时清除 - let themeChangeTimer: ReturnType | null = null + // 抽屉控制 + const useDrawerControl = () => { + // 用于存储 setTimeout 的 ID,以便在需要时清除 + let themeChangeTimer: ReturnType | null = null - // 打开抽屉 - const handleOpen = () => { - // 清除可能存在的旧定时器 - if (themeChangeTimer) { - clearTimeout(themeChangeTimer) - } - // 延迟添加 theme-change class,避免抽屉打开动画受影响 - themeChangeTimer = setTimeout(() => { - domOperations.setBodyClass('theme-change', true) - themeChangeTimer = null - }, 500) - } + // 打开抽屉 + const handleOpen = () => { + // 清除可能存在的旧定时器 + if (themeChangeTimer) { + clearTimeout(themeChangeTimer) + } + // 延迟添加 theme-change class,避免抽屉打开动画受影响 + themeChangeTimer = setTimeout(() => { + domOperations.setBodyClass('theme-change', true) + themeChangeTimer = null + }, 500) + } - // 关闭抽屉 - const handleClose = () => { - // 清除未执行的定时器,防止关闭后才添加 class - if (themeChangeTimer) { - clearTimeout(themeChangeTimer) - themeChangeTimer = null - } - // 立即移除 theme-change class - domOperations.setBodyClass('theme-change', false) - } + // 关闭抽屉 + const handleClose = () => { + // 清除未执行的定时器,防止关闭后才添加 class + if (themeChangeTimer) { + clearTimeout(themeChangeTimer) + themeChangeTimer = null + } + // 立即移除 theme-change class + domOperations.setBodyClass('theme-change', false) + } - // 打开设置 - const openSetting = () => { - showDrawer.value = true - } + // 打开设置 + const openSetting = () => { + showDrawer.value = true + } - // 关闭设置 - const closeDrawer = () => { - showDrawer.value = false - } + // 关闭设置 + const closeDrawer = () => { + showDrawer.value = false + } - return { - handleOpen, - handleClose, - openSetting, - closeDrawer - } - } + return { + handleOpen, + handleClose, + openSetting, + closeDrawer + } + } - // Props 变化监听 - const usePropsWatcher = (props: { open?: boolean }) => { - watch( - () => props.open, - (val: boolean | undefined) => { - if (val !== undefined) { - showDrawer.value = val - } - } - ) - } + // Props 变化监听 + const usePropsWatcher = (props: { open?: boolean }) => { + watch( + () => props.open, + (val: boolean | undefined) => { + if (val !== undefined) { + showDrawer.value = val + } + } + ) + } - // 初始化设置 - const useSettingsInitializer = () => { - const themeHandlers = useThemeHandlers() - const { openSetting } = useDrawerControl() - const { stopWatch } = useResponsiveLayout() - let themeCleanup: (() => void) | null = null + // 初始化设置 + const useSettingsInitializer = () => { + const themeHandlers = useThemeHandlers() + const { openSetting } = useDrawerControl() + const { stopWatch } = useResponsiveLayout() + let themeCleanup: (() => void) | null = null - const initializeSettings = () => { - mittBus.on('openSetting', openSetting) - themeHandlers.initSystemColor() - themeCleanup = themeHandlers.listenerSystemTheme() - initColorWeak() + const initializeSettings = () => { + mittBus.on('openSetting', openSetting) + themeHandlers.initSystemColor() + themeCleanup = themeHandlers.listenerSystemTheme() + initColorWeak() - // 设置盒子模式 - const boxMode = settingStore.boxBorderMode ? 'border-mode' : 'shadow-mode' - domOperations.setRootAttribute('data-box-mode', boxMode) + // 设置盒子模式 + const boxMode = settingStore.boxBorderMode ? 'border-mode' : 'shadow-mode' + domOperations.setRootAttribute('data-box-mode', boxMode) - themeHandlers.initSystemTheme() - openFestival() - } + themeHandlers.initSystemTheme() + openFestival() + } - const cleanupSettings = () => { - stopWatch() - themeCleanup?.() - cleanup() - } + const cleanupSettings = () => { + stopWatch() + themeCleanup?.() + cleanup() + } - return { - initializeSettings, - cleanupSettings - } - } + return { + initializeSettings, + cleanupSettings + } + } - return { - // 状态 - showDrawer, + return { + // 状态 + showDrawer, - // 方法组合 - useThemeHandlers, - useResponsiveLayout, - useDrawerControl, - usePropsWatcher, - useSettingsInitializer - } + // 方法组合 + useThemeHandlers, + useResponsiveLayout, + useDrawerControl, + usePropsWatcher, + useSettingsInitializer + } } diff --git a/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts b/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts index 65352d2..2fb90cc 100644 --- a/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts +++ b/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts @@ -5,33 +5,33 @@ import { MenuThemeEnum, MenuTypeEnum } from '@/enums/appEnum' * 设置状态管理 */ export function useSettingsState() { - const settingStore = useSettingStore() + const settingStore = useSettingStore() - // 色弱模式初始化 - const initColorWeak = () => { - if (settingStore.colorWeak) { - const el = document.getElementsByTagName('html')[0] - setTimeout(() => { - el.classList.add('color-weak') - }, 100) - } - } + // 色弱模式初始化 + const initColorWeak = () => { + if (settingStore.colorWeak) { + const el = document.getElementsByTagName('html')[0] + setTimeout(() => { + el.classList.add('color-weak') + }, 100) + } + } - // 菜单布局切换 - const switchMenuLayouts = (type: MenuTypeEnum) => { - if (type === MenuTypeEnum.LEFT || type === MenuTypeEnum.TOP_LEFT) { - settingStore.setMenuOpen(true) - } - settingStore.switchMenuLayouts(type) - if (type === MenuTypeEnum.DUAL_MENU) { - settingStore.switchMenuStyles(MenuThemeEnum.DESIGN) - settingStore.setMenuOpen(true) - } - } + // 菜单布局切换 + const switchMenuLayouts = (type: MenuTypeEnum) => { + if (type === MenuTypeEnum.LEFT || type === MenuTypeEnum.TOP_LEFT) { + settingStore.setMenuOpen(true) + } + settingStore.switchMenuLayouts(type) + if (type === MenuTypeEnum.DUAL_MENU) { + settingStore.switchMenuStyles(MenuThemeEnum.DESIGN) + settingStore.setMenuOpen(true) + } + } - return { - // 方法 - initColorWeak, - switchMenuLayouts - } + return { + // 方法 + initColorWeak, + switchMenuLayouts + } } diff --git a/src/components/core/layouts/art-settings-panel/index.vue b/src/components/core/layouts/art-settings-panel/index.vue index 0cbf344..955440b 100644 --- a/src/components/core/layouts/art-settings-panel/index.vue +++ b/src/components/core/layouts/art-settings-panel/index.vue @@ -1,72 +1,72 @@ diff --git a/src/components/core/layouts/art-settings-panel/style.scss b/src/components/core/layouts/art-settings-panel/style.scss index e863074..42e9b76 100644 --- a/src/components/core/layouts/art-settings-panel/style.scss +++ b/src/components/core/layouts/art-settings-panel/style.scss @@ -2,91 +2,91 @@ // 设置抽屉模态框样式 .setting-modal { - background: transparent !important; + background: transparent !important; - .el-drawer { - // 背景滤镜效果 - background: rgba($color: #fff, $alpha: 50%) !important; - box-shadow: 0 0 30px rgb(0 0 0 / 10%) !important; + .el-drawer { + // 背景滤镜效果 + background: rgba($color: #fff, $alpha: 50%) !important; + box-shadow: 0 0 30px rgb(0 0 0 / 10%) !important; - @include backdropBlur(); + @include backdropBlur(); - .setting-box-wrap { - display: flex; - flex-wrap: wrap; - align-items: center; - width: calc(100% + 15px); - margin-bottom: 10px; + .setting-box-wrap { + display: flex; + flex-wrap: wrap; + align-items: center; + width: calc(100% + 15px); + margin-bottom: 10px; - .setting-item { - box-sizing: border-box; - width: calc(33.333% - 15px); - margin-right: 15px; - text-align: center; + .setting-item { + box-sizing: border-box; + width: calc(33.333% - 15px); + margin-right: 15px; + text-align: center; - .box { - position: relative; - box-sizing: border-box; - display: flex; - height: 52px; - overflow: hidden; - cursor: pointer; - border: 2px solid var(--default-border); - border-radius: 8px; - box-shadow: 0 0 8px 0 rgb(0 0 0 / 10%); - transition: box-shadow 0.1s; + .box { + position: relative; + box-sizing: border-box; + display: flex; + height: 52px; + overflow: hidden; + cursor: pointer; + border: 2px solid var(--default-border); + border-radius: 8px; + box-shadow: 0 0 8px 0 rgb(0 0 0 / 10%); + transition: box-shadow 0.1s; - &.mt-16 { - margin-top: 16px; - } + &.mt-16 { + margin-top: 16px; + } - &.is-active { - border: 2px solid var(--theme-color); - } + &.is-active { + border: 2px solid var(--theme-color); + } - img { - width: 100%; - height: 100%; - } - } + img { + width: 100%; + height: 100%; + } + } - .name { - margin-top: 6px; - font-size: 14px; - text-align: center; - } - } - } - } + .name { + margin-top: 6px; + font-size: 14px; + text-align: center; + } + } + } + } - // 去除滚动条 - .el-drawer__body::-webkit-scrollbar { - width: 0 !important; - } + // 去除滚动条 + .el-drawer__body::-webkit-scrollbar { + width: 0 !important; + } } .dark { - .setting-modal { - .el-drawer { - background: rgba($color: #000, $alpha: 50%) !important; + .setting-modal { + .el-drawer { + background: rgba($color: #000, $alpha: 50%) !important; - .setting-item { - .box { - border: 2px solid transparent; - } - } - } - } + .setting-item { + .box { + border: 2px solid transparent; + } + } + } + } } // 去除火狐浏览器滚动条 :deep(.el-drawer__body) { - scrollbar-width: none; + scrollbar-width: none; } // 移动端隐藏 @media screen and (width <= 800px) { - .mobile-hide { - display: none !important; - } + .mobile-hide { + display: none !important; + } } diff --git a/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue b/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue index b6dc9d3..495930e 100644 --- a/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue +++ b/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue @@ -1,77 +1,77 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue b/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue index 86c7a9e..3d01736 100644 --- a/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue +++ b/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue @@ -1,38 +1,38 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue b/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue index 05a4b41..dccb7e9 100644 --- a/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue +++ b/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue @@ -1,35 +1,35 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue b/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue index 1f5be72..bae9bcc 100644 --- a/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue +++ b/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue @@ -1,33 +1,33 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue b/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue index dbcae46..169ca8d 100644 --- a/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue +++ b/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue @@ -1,31 +1,34 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue b/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue index 61237eb..33b18ce 100644 --- a/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue +++ b/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue @@ -1,44 +1,44 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue b/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue index 31ef00c..8ece720 100644 --- a/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue +++ b/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue @@ -1,17 +1,17 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue b/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue index 7b47d1a..302d650 100644 --- a/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue +++ b/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue @@ -1,235 +1,241 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue b/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue index 85372be..3281389 100644 --- a/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue +++ b/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue @@ -1,51 +1,51 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue b/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue index e3ead9e..49299b9 100644 --- a/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue +++ b/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue @@ -1,18 +1,18 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue b/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue index 5721027..5e87c27 100644 --- a/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue +++ b/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue @@ -1,101 +1,105 @@ diff --git a/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue b/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue index 4b46fcd..94fa30c 100644 --- a/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue +++ b/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue @@ -1,28 +1,28 @@ diff --git a/src/components/core/layouts/art-work-tab/index.vue b/src/components/core/layouts/art-work-tab/index.vue index 152ff63..cd47dfd 100644 --- a/src/components/core/layouts/art-work-tab/index.vue +++ b/src/components/core/layouts/art-work-tab/index.vue @@ -1,584 +1,595 @@ diff --git a/src/components/core/media/art-cutter-img/index.vue b/src/components/core/media/art-cutter-img/index.vue index 191ceed..61738f6 100644 --- a/src/components/core/media/art-cutter-img/index.vue +++ b/src/components/core/media/art-cutter-img/index.vue @@ -1,350 +1,350 @@ diff --git a/src/components/core/media/art-video-player/index.vue b/src/components/core/media/art-video-player/index.vue index 4f681ea..d5b2b59 100644 --- a/src/components/core/media/art-video-player/index.vue +++ b/src/components/core/media/art-video-player/index.vue @@ -1,111 +1,111 @@ diff --git a/src/components/core/others/art-menu-right/index.vue b/src/components/core/others/art-menu-right/index.vue index 1cc92ab..ff674b9 100644 --- a/src/components/core/others/art-menu-right/index.vue +++ b/src/components/core/others/art-menu-right/index.vue @@ -1,415 +1,418 @@ diff --git a/src/components/core/others/art-watermark/index.vue b/src/components/core/others/art-watermark/index.vue index 1d7f06b..1f7274e 100644 --- a/src/components/core/others/art-watermark/index.vue +++ b/src/components/core/others/art-watermark/index.vue @@ -1,64 +1,64 @@ diff --git a/src/components/core/tables/art-table-header/index.vue b/src/components/core/tables/art-table-header/index.vue index 788c2b7..e9b1f46 100644 --- a/src/components/core/tables/art-table-header/index.vue +++ b/src/components/core/tables/art-table-header/index.vue @@ -1,328 +1,339 @@ diff --git a/src/components/core/tables/art-table/index.vue b/src/components/core/tables/art-table/index.vue index 2392d96..187d659 100644 --- a/src/components/core/tables/art-table/index.vue +++ b/src/components/core/tables/art-table/index.vue @@ -3,340 +3,341 @@ diff --git a/src/components/core/tables/art-table/style.scss b/src/components/core/tables/art-table/style.scss index 67459e8..17412a2 100644 --- a/src/components/core/tables/art-table/style.scss +++ b/src/components/core/tables/art-table/style.scss @@ -1,99 +1,99 @@ .art-table { - position: relative; - height: 100%; + position: relative; + height: 100%; - .el-table { - height: 100%; - margin-top: 10px; - } + .el-table { + height: 100%; + margin-top: 10px; + } - :deep(.el-loading-mask) { - z-index: 100; - background-color: var(--default-box-color) !important; - } + :deep(.el-loading-mask) { + z-index: 100; + background-color: var(--default-box-color) !important; + } - // Loading 过渡动画 - 消失时淡出 - .loading-fade-leave-active { - transition: opacity 0.3s ease-out; - } + // Loading 过渡动画 - 消失时淡出 + .loading-fade-leave-active { + transition: opacity 0.3s ease-out; + } - .loading-fade-leave-to { - opacity: 0; - } + .loading-fade-leave-to { + opacity: 0; + } - // 空状态垂直居中 - &.is-empty { - :deep(.el-scrollbar__wrap) { - display: flex; - } - } + // 空状态垂直居中 + &.is-empty { + :deep(.el-scrollbar__wrap) { + display: flex; + } + } - .pagination { - display: flex; - margin-top: 13px; + .pagination { + display: flex; + margin-top: 13px; - :deep(.el-select) { - width: 102px !important; - } + :deep(.el-select) { + width: 102px !important; + } - // 分页对齐方式 - &.left { - justify-content: flex-start; - } + // 分页对齐方式 + &.left { + justify-content: flex-start; + } - &.center { - justify-content: center; - } + &.center { + justify-content: center; + } - &.right { - justify-content: flex-end; - } + &.right { + justify-content: flex-end; + } - // 自定义分页组件样式 - &.custom-pagination { - :deep(.el-pagination) { - .btn-prev, - .btn-next { - background-color: transparent; - border: 1px solid var(--art-gray-300); - transition: border-color 0.15s; + // 自定义分页组件样式 + &.custom-pagination { + :deep(.el-pagination) { + .btn-prev, + .btn-next { + background-color: transparent; + border: 1px solid var(--art-gray-300); + transition: border-color 0.15s; - &:hover:not(.is-disabled) { - color: var(--theme-color); - border-color: var(--theme-color); - } - } + &:hover:not(.is-disabled) { + color: var(--theme-color); + border-color: var(--theme-color); + } + } - li { - box-sizing: border-box; - font-weight: 400 !important; - background-color: transparent; - border: 1px solid var(--art-gray-300); - transition: border-color 0.15s; + li { + box-sizing: border-box; + font-weight: 400 !important; + background-color: transparent; + border: 1px solid var(--art-gray-300); + transition: border-color 0.15s; - &.is-active { - font-weight: 400; - color: #fff; - background-color: var(--theme-color); - border: 1px solid var(--theme-color); - } + &.is-active { + font-weight: 400; + color: #fff; + background-color: var(--theme-color); + border: 1px solid var(--theme-color); + } - &:hover:not(.is-disabled) { - border-color: var(--theme-color); - } - } - } - } - } + &:hover:not(.is-disabled) { + border-color: var(--theme-color); + } + } + } + } + } } // 移动端分页 @media (width <= 640px) { - :deep(.el-pagination) { - display: flex; - flex-wrap: wrap; - gap: 15px 0; - align-items: center; - justify-content: center; - } + :deep(.el-pagination) { + display: flex; + flex-wrap: wrap; + gap: 15px 0; + align-items: center; + justify-content: center; + } } diff --git a/src/components/core/text-effect/art-count-to/index.vue b/src/components/core/text-effect/art-count-to/index.vue index 7fb104b..435b21b 100644 --- a/src/components/core/text-effect/art-count-to/index.vue +++ b/src/components/core/text-effect/art-count-to/index.vue @@ -1,310 +1,319 @@ diff --git a/src/components/core/text-effect/art-festival-text-scroll/index.vue b/src/components/core/text-effect/art-festival-text-scroll/index.vue index 770b457..4c56993 100644 --- a/src/components/core/text-effect/art-festival-text-scroll/index.vue +++ b/src/components/core/text-effect/art-festival-text-scroll/index.vue @@ -1,32 +1,32 @@ diff --git a/src/components/core/text-effect/art-text-scroll/index.vue b/src/components/core/text-effect/art-text-scroll/index.vue index 90be30f..b2e2af4 100644 --- a/src/components/core/text-effect/art-text-scroll/index.vue +++ b/src/components/core/text-effect/art-text-scroll/index.vue @@ -1,285 +1,285 @@ diff --git a/src/components/core/theme/theme-svg/index.vue b/src/components/core/theme/theme-svg/index.vue index 0b565a9..f6c59d5 100644 --- a/src/components/core/theme/theme-svg/index.vue +++ b/src/components/core/theme/theme-svg/index.vue @@ -1,100 +1,100 @@ diff --git a/src/components/core/views/exception/ArtException.vue b/src/components/core/views/exception/ArtException.vue index 699228f..62cc838 100644 --- a/src/components/core/views/exception/ArtException.vue +++ b/src/components/core/views/exception/ArtException.vue @@ -1,43 +1,43 @@ diff --git a/src/components/core/views/login/AuthTopBar.vue b/src/components/core/views/login/AuthTopBar.vue index 9455253..0d5fb06 100644 --- a/src/components/core/views/login/AuthTopBar.vue +++ b/src/components/core/views/login/AuthTopBar.vue @@ -1,149 +1,161 @@ diff --git a/src/components/core/views/login/LoginLeftView.vue b/src/components/core/views/login/LoginLeftView.vue index af6a904..a2acad3 100644 --- a/src/components/core/views/login/LoginLeftView.vue +++ b/src/components/core/views/login/LoginLeftView.vue @@ -1,602 +1,615 @@ diff --git a/src/components/core/views/result/ArtResultPage.vue b/src/components/core/views/result/ArtResultPage.vue index b2eca48..092b7fb 100644 --- a/src/components/core/views/result/ArtResultPage.vue +++ b/src/components/core/views/result/ArtResultPage.vue @@ -1,43 +1,43 @@ diff --git a/src/components/core/widget/art-icon-button/index.vue b/src/components/core/widget/art-icon-button/index.vue index 760888b..11c7591 100644 --- a/src/components/core/widget/art-icon-button/index.vue +++ b/src/components/core/widget/art-icon-button/index.vue @@ -1,23 +1,23 @@ diff --git a/src/config/assets/images.ts b/src/config/assets/images.ts index f3e89dd..f451e4e 100644 --- a/src/config/assets/images.ts +++ b/src/config/assets/images.ts @@ -29,33 +29,33 @@ import lightStyle from '@imgs/settings/menu_styles/light.png' * 配置中心图片资源对象 */ export const configImages = { - /** 系统主题预览图 */ - themeStyles: { - /** 亮色主题 */ - light: lightTheme, - /** 暗色主题 */ - dark: darkTheme, - /** 自动主题(跟随系统) */ - system: systemTheme - }, - /** 菜单布局预览图 */ - menuLayouts: { - /** 左侧菜单 */ - vertical: verticalLayout, - /** 顶部菜单 */ - horizontal: horizontalLayout, - /** 混合菜单 */ - mixed: mixedLayout, - /** 双栏菜单 */ - dualColumn: dualColumnLayout - }, - /** 菜单风格预览图 */ - menuStyles: { - /** 设计风格 */ - design: designStyle, - /** 暗色风格 */ - dark: darkStyle, - /** 亮色风格 */ - light: lightStyle - } + /** 系统主题预览图 */ + themeStyles: { + /** 亮色主题 */ + light: lightTheme, + /** 暗色主题 */ + dark: darkTheme, + /** 自动主题(跟随系统) */ + system: systemTheme + }, + /** 菜单布局预览图 */ + menuLayouts: { + /** 左侧菜单 */ + vertical: verticalLayout, + /** 顶部菜单 */ + horizontal: horizontalLayout, + /** 混合菜单 */ + mixed: mixedLayout, + /** 双栏菜单 */ + dualColumn: dualColumnLayout + }, + /** 菜单风格预览图 */ + menuStyles: { + /** 设计风格 */ + design: designStyle, + /** 暗色风格 */ + dark: darkStyle, + /** 亮色风格 */ + light: lightStyle + } } diff --git a/src/config/fastEnter.ts b/src/config/fastEnter.ts index ccade16..78cccf2 100644 --- a/src/config/fastEnter.ts +++ b/src/config/fastEnter.ts @@ -6,74 +6,74 @@ import { WEB_LINKS } from '@/utils/constants' import type { FastEnterConfig } from '@/types/config' const fastEnterConfig: FastEnterConfig = { - // 显示条件(屏幕宽度) - minWidth: 1200, - // 应用列表 - applications: [ - { - name: '工作台', - description: '系统概览与数据统计', - icon: 'ri:pie-chart-line', - iconColor: '#377dff', - enabled: true, - order: 1, - routeName: 'Console' - }, - { - name: '官方文档', - description: '使用指南与开发文档', - icon: 'ri:bill-line', - iconColor: '#ffb100', - enabled: true, - order: 2, - link: WEB_LINKS.DOCS - }, - { - name: '技术支持', - description: '技术支持与问题反馈', - icon: 'ri:user-location-line', - iconColor: '#ff6b6b', - enabled: true, - order: 3, - link: WEB_LINKS.COMMUNITY - }, - { - name: '哔哩哔哩', - description: '技术分享与交流', - icon: 'ri:bilibili-line', - iconColor: '#FB7299', - enabled: true, - order: 4, - link: WEB_LINKS.BILIBILI - } - ], - // 快速链接 - quickLinks: [ - { - name: '登录', - enabled: true, - order: 1, - routeName: 'Login' - }, - { - name: '注册', - enabled: true, - order: 2, - routeName: 'Register' - }, - { - name: '忘记密码', - enabled: true, - order: 3, - routeName: 'ForgetPassword' - }, - { - name: '个人中心', - enabled: true, - order: 4, - routeName: 'UserCenter' - } - ] + // 显示条件(屏幕宽度) + minWidth: 1200, + // 应用列表 + applications: [ + { + name: '工作台', + description: '系统概览与数据统计', + icon: 'ri:pie-chart-line', + iconColor: '#377dff', + enabled: true, + order: 1, + routeName: 'Console' + }, + { + name: '官方文档', + description: '使用指南与开发文档', + icon: 'ri:bill-line', + iconColor: '#ffb100', + enabled: true, + order: 2, + link: WEB_LINKS.DOCS + }, + { + name: '技术支持', + description: '技术支持与问题反馈', + icon: 'ri:user-location-line', + iconColor: '#ff6b6b', + enabled: true, + order: 3, + link: WEB_LINKS.COMMUNITY + }, + { + name: '哔哩哔哩', + description: '技术分享与交流', + icon: 'ri:bilibili-line', + iconColor: '#FB7299', + enabled: true, + order: 4, + link: WEB_LINKS.BILIBILI + } + ], + // 快速链接 + quickLinks: [ + { + name: '登录', + enabled: true, + order: 1, + routeName: 'Login' + }, + { + name: '注册', + enabled: true, + order: 2, + routeName: 'Register' + }, + { + name: '忘记密码', + enabled: true, + order: 3, + routeName: 'ForgetPassword' + }, + { + name: '个人中心', + enabled: true, + order: 4, + routeName: 'UserCenter' + } + ] } export default Object.freeze(fastEnterConfig) diff --git a/src/config/index.ts b/src/config/index.ts index daa623a..2e0bcbe 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -36,100 +36,104 @@ import fastEnterConfig from './modules/fastEnter' import { headerBarConfig } from './modules/headerBar' const appConfig: SystemConfig = { - // 系统信息 - systemInfo: { - name: 'Art Design Pro' // 系统名称 - }, - // 系统主题 - systemThemeStyles: { - [SystemThemeEnum.LIGHT]: { className: '' }, - [SystemThemeEnum.DARK]: { className: SystemThemeEnum.DARK } - }, - // 系统主题列表 - settingThemeList: [ - { - name: 'Light', - theme: SystemThemeEnum.LIGHT, - color: ['#fff', '#fff'], - leftLineColor: '#EDEEF0', - rightLineColor: '#EDEEF0', - img: configImages.themeStyles.light - }, - { - name: 'Dark', - theme: SystemThemeEnum.DARK, - color: ['#22252A'], - leftLineColor: '#3F4257', - rightLineColor: '#3F4257', - img: configImages.themeStyles.dark - }, - { - name: 'System', - theme: SystemThemeEnum.AUTO, - color: ['#fff', '#22252A'], - leftLineColor: '#EDEEF0', - rightLineColor: '#3F4257', - img: configImages.themeStyles.system - } - ], - // 菜单布局列表 - menuLayoutList: [ - { name: 'Left', value: MenuTypeEnum.LEFT, img: configImages.menuLayouts.vertical }, - { name: 'Top', value: MenuTypeEnum.TOP, img: configImages.menuLayouts.horizontal }, - { name: 'Mixed', value: MenuTypeEnum.TOP_LEFT, img: configImages.menuLayouts.mixed }, - { name: 'Dual Column', value: MenuTypeEnum.DUAL_MENU, img: configImages.menuLayouts.dualColumn } - ], - // 菜单主题列表 - themeList: [ - { - theme: MenuThemeEnum.DESIGN, - background: '#FFFFFF', - systemNameColor: 'var(--art-gray-800)', - iconColor: '#6B6B6B', - textColor: '#29343D', - img: configImages.menuStyles.design - }, - { - theme: MenuThemeEnum.DARK, - background: '#191A23', - systemNameColor: '#D9DADB', - iconColor: '#BABBBD', - textColor: '#BABBBD', - img: configImages.menuStyles.dark - }, - { - theme: MenuThemeEnum.LIGHT, - background: '#ffffff', - systemNameColor: 'var(--art-gray-800)', - iconColor: '#6B6B6B', - textColor: '#29343D', - img: configImages.menuStyles.light - } - ], - // 暗黑模式菜单样式 - darkMenuStyles: [ - { - theme: MenuThemeEnum.DARK, - background: 'var(--default-box-color)', - systemNameColor: '#DDDDDD', - iconColor: '#BABBBD', - textColor: 'rgba(#FFFFFF, 0.7)' - } - ], - // 系统主色 - systemMainColor: [ - '#5D87FF', - '#B48DF3', - '#1D84FF', - '#60C041', - '#38C0FC', - '#F9901F', - '#FF80C8' - ] as const, - // 快速入口配置 - fastEnter: fastEnterConfig, - // 顶部栏功能配置 - headerBar: headerBarConfig + // 系统信息 + systemInfo: { + name: 'Art Design Pro' // 系统名称 + }, + // 系统主题 + systemThemeStyles: { + [SystemThemeEnum.LIGHT]: { className: '' }, + [SystemThemeEnum.DARK]: { className: SystemThemeEnum.DARK } + }, + // 系统主题列表 + settingThemeList: [ + { + name: 'Light', + theme: SystemThemeEnum.LIGHT, + color: ['#fff', '#fff'], + leftLineColor: '#EDEEF0', + rightLineColor: '#EDEEF0', + img: configImages.themeStyles.light + }, + { + name: 'Dark', + theme: SystemThemeEnum.DARK, + color: ['#22252A'], + leftLineColor: '#3F4257', + rightLineColor: '#3F4257', + img: configImages.themeStyles.dark + }, + { + name: 'System', + theme: SystemThemeEnum.AUTO, + color: ['#fff', '#22252A'], + leftLineColor: '#EDEEF0', + rightLineColor: '#3F4257', + img: configImages.themeStyles.system + } + ], + // 菜单布局列表 + menuLayoutList: [ + { name: 'Left', value: MenuTypeEnum.LEFT, img: configImages.menuLayouts.vertical }, + { name: 'Top', value: MenuTypeEnum.TOP, img: configImages.menuLayouts.horizontal }, + { name: 'Mixed', value: MenuTypeEnum.TOP_LEFT, img: configImages.menuLayouts.mixed }, + { + name: 'Dual Column', + value: MenuTypeEnum.DUAL_MENU, + img: configImages.menuLayouts.dualColumn + } + ], + // 菜单主题列表 + themeList: [ + { + theme: MenuThemeEnum.DESIGN, + background: '#FFFFFF', + systemNameColor: 'var(--art-gray-800)', + iconColor: '#6B6B6B', + textColor: '#29343D', + img: configImages.menuStyles.design + }, + { + theme: MenuThemeEnum.DARK, + background: '#191A23', + systemNameColor: '#D9DADB', + iconColor: '#BABBBD', + textColor: '#BABBBD', + img: configImages.menuStyles.dark + }, + { + theme: MenuThemeEnum.LIGHT, + background: '#ffffff', + systemNameColor: 'var(--art-gray-800)', + iconColor: '#6B6B6B', + textColor: '#29343D', + img: configImages.menuStyles.light + } + ], + // 暗黑模式菜单样式 + darkMenuStyles: [ + { + theme: MenuThemeEnum.DARK, + background: 'var(--default-box-color)', + systemNameColor: '#DDDDDD', + iconColor: '#BABBBD', + textColor: 'rgba(#FFFFFF, 0.7)' + } + ], + // 系统主色 + systemMainColor: [ + '#5D87FF', + '#B48DF3', + '#1D84FF', + '#60C041', + '#38C0FC', + '#F9901F', + '#FF80C8' + ] as const, + // 快速入口配置 + fastEnter: fastEnterConfig, + // 顶部栏功能配置 + headerBar: headerBarConfig } export default Object.freeze(appConfig) diff --git a/src/config/modules/component.ts b/src/config/modules/component.ts index bc709e0..b5cfd0a 100644 --- a/src/config/modules/component.ts +++ b/src/config/modules/component.ts @@ -21,70 +21,70 @@ import { defineAsyncComponent } from 'vue' * 全局组件配置列表 */ export const globalComponentsConfig: GlobalComponentConfig[] = [ - { - name: '设置面板', - key: 'settings-panel', - component: defineAsyncComponent( - () => import('@/components/core/layouts/art-settings-panel/index.vue') - ), - enabled: true - }, - { - name: '全局搜索', - key: 'global-search', - component: defineAsyncComponent( - () => import('@/components/core/layouts/art-global-search/index.vue') - ), - enabled: true - }, - { - name: '锁屏', - key: 'screen-lock', - component: defineAsyncComponent( - () => import('@/components/core/layouts/art-screen-lock/index.vue') - ), - enabled: true - }, - { - name: '聊天窗口', - key: 'chat-window', - component: defineAsyncComponent( - () => import('@/components/core/layouts/art-chat-window/index.vue') - ), - enabled: true - }, - { - name: '礼花效果', - key: 'fireworks-effect', - component: defineAsyncComponent( - () => import('@/components/core/layouts/art-fireworks-effect/index.vue') - ), - enabled: true - }, - { - name: '水印效果', - key: 'watermark', - component: defineAsyncComponent( - () => import('@/components/core/others/art-watermark/index.vue') - ), - enabled: true - } + { + name: '设置面板', + key: 'settings-panel', + component: defineAsyncComponent( + () => import('@/components/core/layouts/art-settings-panel/index.vue') + ), + enabled: true + }, + { + name: '全局搜索', + key: 'global-search', + component: defineAsyncComponent( + () => import('@/components/core/layouts/art-global-search/index.vue') + ), + enabled: true + }, + { + name: '锁屏', + key: 'screen-lock', + component: defineAsyncComponent( + () => import('@/components/core/layouts/art-screen-lock/index.vue') + ), + enabled: true + }, + { + name: '聊天窗口', + key: 'chat-window', + component: defineAsyncComponent( + () => import('@/components/core/layouts/art-chat-window/index.vue') + ), + enabled: true + }, + { + name: '礼花效果', + key: 'fireworks-effect', + component: defineAsyncComponent( + () => import('@/components/core/layouts/art-fireworks-effect/index.vue') + ), + enabled: true + }, + { + name: '水印效果', + key: 'watermark', + component: defineAsyncComponent( + () => import('@/components/core/others/art-watermark/index.vue') + ), + enabled: true + } ] /** * 全局组件配置接口 */ export interface GlobalComponentConfig { - /** 组件名称 */ - name: string - /** 组件标识 */ - key: string - /** 组件 */ - component: any - /** 是否启用 */ - enabled?: boolean - /** 组件描述 */ - description?: string + /** 组件名称 */ + name: string + /** 组件标识 */ + key: string + /** 组件 */ + component: any + /** 是否启用 */ + enabled?: boolean + /** 组件描述 */ + description?: string } /** @@ -92,7 +92,7 @@ export interface GlobalComponentConfig { * @returns 已启用的组件配置列表 */ export const getEnabledGlobalComponents = () => { - return globalComponentsConfig.filter((config) => config.enabled !== false) + return globalComponentsConfig.filter((config) => config.enabled !== false) } /** @@ -101,5 +101,5 @@ export const getEnabledGlobalComponents = () => { * @returns 组件配置对象 */ export const getGlobalComponentByKey = (key: string) => { - return globalComponentsConfig.find((config) => config.key === key) + return globalComponentsConfig.find((config) => config.key === key) } diff --git a/src/config/modules/fastEnter.ts b/src/config/modules/fastEnter.ts index 6b9740c..1ed13ac 100644 --- a/src/config/modules/fastEnter.ts +++ b/src/config/modules/fastEnter.ts @@ -6,122 +6,122 @@ import { WEB_LINKS } from '@/utils/constants' import type { FastEnterConfig } from '@/types/config' const fastEnterConfig: FastEnterConfig = { - // 显示条件(屏幕宽度) - minWidth: 1200, - // 应用列表 - applications: [ - { - name: '工作台', - description: '系统概览与数据统计', - icon: 'ri:pie-chart-line', - iconColor: '#377dff', - enabled: true, - order: 1, - routeName: 'Console' - }, - { - name: '分析页', - description: '数据分析与可视化', - icon: 'ri:game-line', - iconColor: '#ff3b30', - enabled: true, - order: 2, - routeName: 'Analysis' - }, - { - name: '礼花效果', - description: '动画特效展示', - icon: 'ri:loader-line', - iconColor: '#7A7FFF', - enabled: true, - order: 3, - routeName: 'Fireworks' - }, - { - name: '聊天', - description: '即时通讯功能', - icon: 'ri:user-line', - iconColor: '#13DEB9', - enabled: true, - order: 4, - routeName: 'Chat' - }, - { - name: '官方文档', - description: '使用指南与开发文档', - icon: 'ri:bill-line', - iconColor: '#ffb100', - enabled: true, - order: 5, - link: WEB_LINKS.DOCS - }, - { - name: '技术支持', - description: '技术支持与问题反馈', - icon: 'ri:user-location-line', - iconColor: '#ff6b6b', - enabled: true, - order: 6, - link: WEB_LINKS.COMMUNITY - }, - { - name: '更新日志', - description: '版本更新与变更记录', - icon: 'ri:gamepad-line', - iconColor: '#38C0FC', - enabled: true, - order: 7, - routeName: 'ChangeLog' - }, - { - name: '哔哩哔哩', - description: '技术分享与交流', - icon: 'ri:bilibili-line', - iconColor: '#FB7299', - enabled: true, - order: 8, - link: WEB_LINKS.BILIBILI - } - ], - // 快速链接 - quickLinks: [ - { - name: '登录', - enabled: true, - order: 1, - routeName: 'Login' - }, - { - name: '注册', - enabled: true, - order: 2, - routeName: 'Register' - }, - { - name: '忘记密码', - enabled: true, - order: 3, - routeName: 'ForgetPassword' - }, - { - name: '定价', - enabled: true, - order: 4, - routeName: 'Pricing' - }, - { - name: '个人中心', - enabled: true, - order: 5, - routeName: 'UserCenter' - }, - { - name: '留言管理', - enabled: true, - order: 6, - routeName: 'ArticleComment' - } - ] + // 显示条件(屏幕宽度) + minWidth: 1200, + // 应用列表 + applications: [ + { + name: '工作台', + description: '系统概览与数据统计', + icon: 'ri:pie-chart-line', + iconColor: '#377dff', + enabled: true, + order: 1, + routeName: 'Console' + }, + { + name: '分析页', + description: '数据分析与可视化', + icon: 'ri:game-line', + iconColor: '#ff3b30', + enabled: true, + order: 2, + routeName: 'Analysis' + }, + { + name: '礼花效果', + description: '动画特效展示', + icon: 'ri:loader-line', + iconColor: '#7A7FFF', + enabled: true, + order: 3, + routeName: 'Fireworks' + }, + { + name: '聊天', + description: '即时通讯功能', + icon: 'ri:user-line', + iconColor: '#13DEB9', + enabled: true, + order: 4, + routeName: 'Chat' + }, + { + name: '官方文档', + description: '使用指南与开发文档', + icon: 'ri:bill-line', + iconColor: '#ffb100', + enabled: true, + order: 5, + link: WEB_LINKS.DOCS + }, + { + name: '技术支持', + description: '技术支持与问题反馈', + icon: 'ri:user-location-line', + iconColor: '#ff6b6b', + enabled: true, + order: 6, + link: WEB_LINKS.COMMUNITY + }, + { + name: '更新日志', + description: '版本更新与变更记录', + icon: 'ri:gamepad-line', + iconColor: '#38C0FC', + enabled: true, + order: 7, + routeName: 'ChangeLog' + }, + { + name: '哔哩哔哩', + description: '技术分享与交流', + icon: 'ri:bilibili-line', + iconColor: '#FB7299', + enabled: true, + order: 8, + link: WEB_LINKS.BILIBILI + } + ], + // 快速链接 + quickLinks: [ + { + name: '登录', + enabled: true, + order: 1, + routeName: 'Login' + }, + { + name: '注册', + enabled: true, + order: 2, + routeName: 'Register' + }, + { + name: '忘记密码', + enabled: true, + order: 3, + routeName: 'ForgetPassword' + }, + { + name: '定价', + enabled: true, + order: 4, + routeName: 'Pricing' + }, + { + name: '个人中心', + enabled: true, + order: 5, + routeName: 'UserCenter' + }, + { + name: '留言管理', + enabled: true, + order: 6, + routeName: 'ArticleComment' + } + ] } export default Object.freeze(fastEnterConfig) diff --git a/src/config/modules/festival.ts b/src/config/modules/festival.ts index 39cd790..e1b773f 100644 --- a/src/config/modules/festival.ts +++ b/src/config/modules/festival.ts @@ -30,22 +30,22 @@ import { FestivalConfig } from '@/types/config' // import yd from '@imgs/ceremony/yd.png' export const festivalConfigList: FestivalConfig[] = [ - // 跨日期示例 - // { - // name: 'v3.0 Sass 升级至 TailwindCSS', - // date: '2025-11-03', - // endDate: '2025-11-09', - // image: '', - // count: 3, - // scrollText: - // '🚀 系统 v3.0 测试阶段正式开启!测试周期为 11 月 3 日 - 11 月 16 日,通过 TailwindCSS 重构样式体系、统一 Iconify 图标方案,带来更高效现代的开发体验,正式发布敬请期待~' - // } - // 单日示例:圣诞节 - // { - // name: '圣诞节', - // date: '2024-12-25', - // image: sd, - // count: 3 // 可选,不设置则使用默认值 3 次 - // scrollText: 'Merry Christmas!Art Design Pro 祝您圣诞快乐,愿节日的欢乐与祝福如雪花般纷至沓来!', - // } + // 跨日期示例 + // { + // name: 'v3.0 Sass 升级至 TailwindCSS', + // date: '2025-11-03', + // endDate: '2025-11-09', + // image: '', + // count: 3, + // scrollText: + // '🚀 系统 v3.0 测试阶段正式开启!测试周期为 11 月 3 日 - 11 月 16 日,通过 TailwindCSS 重构样式体系、统一 Iconify 图标方案,带来更高效现代的开发体验,正式发布敬请期待~' + // } + // 单日示例:圣诞节 + // { + // name: '圣诞节', + // date: '2024-12-25', + // image: sd, + // count: 3 // 可选,不设置则使用默认值 3 次 + // scrollText: 'Merry Christmas!Art Design Pro 祝您圣诞快乐,愿节日的欢乐与祝福如雪花般纷至沓来!', + // } ] diff --git a/src/config/modules/headerBar.ts b/src/config/modules/headerBar.ts index a420e82..279868e 100644 --- a/src/config/modules/headerBar.ts +++ b/src/config/modules/headerBar.ts @@ -14,50 +14,50 @@ import { HeaderBarFeatureConfig } from '@/types' * 顶部栏功能配置对象 */ export const headerBarConfig: HeaderBarFeatureConfig = { - menuButton: { - enabled: true, - description: '控制左侧菜单的展开/收起按钮' - }, - refreshButton: { - enabled: true, - description: '页面刷新按钮' - }, - fastEnter: { - enabled: true, - description: '快速入口功能,提供常用应用和链接的快速访问' - }, - breadcrumb: { - enabled: true, - description: '面包屑导航,显示当前页面路径' - }, - globalSearch: { - enabled: true, - description: '全局搜索功能,支持快捷键 Ctrl+K 或 Cmd+K' - }, - fullscreen: { - enabled: true, - description: '全屏切换功能' - }, - notification: { - enabled: true, - description: '通知中心,显示系统通知和消息' - }, - chat: { - enabled: true, - description: '聊天功能,提供实时沟通' - }, - language: { - enabled: true, - description: '多语言切换功能' - }, - settings: { - enabled: true, - description: '系统设置面板' - }, - themeToggle: { - enabled: true, - description: '主题切换功能(明暗主题)' - } + menuButton: { + enabled: true, + description: '控制左侧菜单的展开/收起按钮' + }, + refreshButton: { + enabled: true, + description: '页面刷新按钮' + }, + fastEnter: { + enabled: true, + description: '快速入口功能,提供常用应用和链接的快速访问' + }, + breadcrumb: { + enabled: true, + description: '面包屑导航,显示当前页面路径' + }, + globalSearch: { + enabled: true, + description: '全局搜索功能,支持快捷键 Ctrl+K 或 Cmd+K' + }, + fullscreen: { + enabled: true, + description: '全屏切换功能' + }, + notification: { + enabled: true, + description: '通知中心,显示系统通知和消息' + }, + chat: { + enabled: true, + description: '聊天功能,提供实时沟通' + }, + language: { + enabled: true, + description: '多语言切换功能' + }, + settings: { + enabled: true, + description: '系统设置面板' + }, + themeToggle: { + enabled: true, + description: '主题切换功能(明暗主题)' + } } export default headerBarConfig diff --git a/src/config/setting.ts b/src/config/setting.ts index 94f2d2c..71ad251 100644 --- a/src/config/setting.ts +++ b/src/config/setting.ts @@ -27,64 +27,64 @@ import { SystemThemeEnum, MenuThemeEnum, MenuTypeEnum, ContainerWidthEnum } from * 系统设置默认值配置 */ export const SETTING_DEFAULT_CONFIG = { - /** 菜单类型 */ - menuType: MenuTypeEnum.LEFT, - /** 菜单展开宽度 */ - menuOpenWidth: 230, - /** 菜单是否展开 */ - menuOpen: true, - /** 双菜单是否显示文本 */ - dualMenuShowText: false, - /** 系统主题类型 */ - systemThemeType: SystemThemeEnum.AUTO, - /** 系统主题模式 */ - systemThemeMode: SystemThemeEnum.AUTO, - /** 菜单风格 */ - menuThemeType: MenuThemeEnum.DESIGN, - /** 系统主题颜色 */ - systemThemeColor: AppConfig.systemMainColor[0], - /** 是否显示菜单按钮 */ - showMenuButton: true, - /** 是否显示快速入口 */ - showFastEnter: true, - /** 是否显示刷新按钮 */ - showRefreshButton: true, - /** 是否显示面包屑 */ - showCrumbs: true, - /** 是否显示工作台标签 */ - showWorkTab: true, - /** 是否显示语言切换 */ - showLanguage: true, - /** 是否显示进度条 */ - showNprogress: false, - /** 是否显示设置引导 */ - showSettingGuide: true, - /** 是否显示节日文本 */ - showFestivalText: false, - /** 是否显示水印 */ - watermarkVisible: false, - /** 是否自动关闭 */ - autoClose: false, - /** 是否唯一展开 */ - uniqueOpened: true, - /** 是否色弱模式 */ - colorWeak: false, - /** 是否刷新 */ - refresh: false, - /** 是否加载节日烟花 */ - holidayFireworksLoaded: false, - /** 边框模式 */ - boxBorderMode: true, - /** 页面过渡效果 */ - pageTransition: 'slide-left', - /** 标签页样式 */ - tabStyle: 'tab-default', - /** 自定义圆角 */ - customRadius: '0.75', - /** 容器宽度 */ - containerWidth: ContainerWidthEnum.FULL, - /** 节日日期 */ - festivalDate: '' + /** 菜单类型 */ + menuType: MenuTypeEnum.LEFT, + /** 菜单展开宽度 */ + menuOpenWidth: 230, + /** 菜单是否展开 */ + menuOpen: true, + /** 双菜单是否显示文本 */ + dualMenuShowText: false, + /** 系统主题类型 */ + systemThemeType: SystemThemeEnum.AUTO, + /** 系统主题模式 */ + systemThemeMode: SystemThemeEnum.AUTO, + /** 菜单风格 */ + menuThemeType: MenuThemeEnum.DESIGN, + /** 系统主题颜色 */ + systemThemeColor: AppConfig.systemMainColor[0], + /** 是否显示菜单按钮 */ + showMenuButton: true, + /** 是否显示快速入口 */ + showFastEnter: true, + /** 是否显示刷新按钮 */ + showRefreshButton: true, + /** 是否显示面包屑 */ + showCrumbs: true, + /** 是否显示工作台标签 */ + showWorkTab: true, + /** 是否显示语言切换 */ + showLanguage: true, + /** 是否显示进度条 */ + showNprogress: false, + /** 是否显示设置引导 */ + showSettingGuide: true, + /** 是否显示节日文本 */ + showFestivalText: false, + /** 是否显示水印 */ + watermarkVisible: false, + /** 是否自动关闭 */ + autoClose: false, + /** 是否唯一展开 */ + uniqueOpened: true, + /** 是否色弱模式 */ + colorWeak: false, + /** 是否刷新 */ + refresh: false, + /** 是否加载节日烟花 */ + holidayFireworksLoaded: false, + /** 边框模式 */ + boxBorderMode: true, + /** 页面过渡效果 */ + pageTransition: 'slide-left', + /** 标签页样式 */ + tabStyle: 'tab-default', + /** 自定义圆角 */ + customRadius: '0.75', + /** 容器宽度 */ + containerWidth: ContainerWidthEnum.FULL, + /** 节日日期 */ + festivalDate: '' } /** @@ -92,7 +92,7 @@ export const SETTING_DEFAULT_CONFIG = { * @returns 设置默认值对象 */ export function getSettingDefaults() { - return { ...SETTING_DEFAULT_CONFIG } + return { ...SETTING_DEFAULT_CONFIG } } /** @@ -100,10 +100,10 @@ export function getSettingDefaults() { * @param currentSettings 当前设置对象 */ export function resetToDefaults(currentSettings: Record) { - const defaults = getSettingDefaults() - Object.keys(defaults).forEach((key) => { - if (key in currentSettings) { - currentSettings[key] = defaults[key as keyof typeof defaults] - } - }) + const defaults = getSettingDefaults() + Object.keys(defaults).forEach((key) => { + if (key in currentSettings) { + currentSettings[key] = defaults[key as keyof typeof defaults] + } + }) } diff --git a/src/directives/business/highlight.ts b/src/directives/business/highlight.ts index 13af225..e2a4528 100644 --- a/src/directives/business/highlight.ts +++ b/src/directives/business/highlight.ts @@ -46,203 +46,203 @@ import hljs from 'highlight.js' // 高亮代码 function highlightCode(block: HTMLElement) { - hljs.highlightElement(block) + hljs.highlightElement(block) } // 插入行号 function insertLineNumbers(block: HTMLElement) { - const lines = block.innerHTML.split('\n') - const numberedLines = lines - .map((line, index) => { - return `${index + 1} ${line}` - }) - .join('\n') - block.innerHTML = numberedLines + const lines = block.innerHTML.split('\n') + const numberedLines = lines + .map((line, index) => { + return `${index + 1} ${line}` + }) + .join('\n') + block.innerHTML = numberedLines } // 添加复制按钮:调整 DOM 结构,将代码部分包裹在 .code-wrapper 内 function addCopyButton(block: HTMLElement) { - const copyButton = document.createElement('i') - copyButton.className = 'copy-button' - copyButton.innerHTML = - '' - copyButton.onclick = () => { - // 过滤掉行号,只复制代码内容 - const codeContent = block.innerText.replace(/^\d+\s+/gm, '') - navigator.clipboard.writeText(codeContent).then(() => { - ElMessage.success('复制成功') - }) - } + const copyButton = document.createElement('i') + copyButton.className = 'copy-button' + copyButton.innerHTML = + '' + copyButton.onclick = () => { + // 过滤掉行号,只复制代码内容 + const codeContent = block.innerText.replace(/^\d+\s+/gm, '') + navigator.clipboard.writeText(codeContent).then(() => { + ElMessage.success('复制成功') + }) + } - const preElement = block.parentElement - if (preElement) { - let codeWrapper: HTMLElement - // 如果代码块还没有被包裹,则创建包裹容器 - if (!block.parentElement.classList.contains('code-wrapper')) { - codeWrapper = document.createElement('div') - codeWrapper.className = 'code-wrapper' - preElement.replaceChild(codeWrapper, block) - codeWrapper.appendChild(block) - } else { - codeWrapper = block.parentElement - } - // 将复制按钮添加到 pre 元素(而非 codeWrapper 内),这样它不会随滚动条滚动 - preElement.appendChild(copyButton) - } + const preElement = block.parentElement + if (preElement) { + let codeWrapper: HTMLElement + // 如果代码块还没有被包裹,则创建包裹容器 + if (!block.parentElement.classList.contains('code-wrapper')) { + codeWrapper = document.createElement('div') + codeWrapper.className = 'code-wrapper' + preElement.replaceChild(codeWrapper, block) + codeWrapper.appendChild(block) + } else { + codeWrapper = block.parentElement + } + // 将复制按钮添加到 pre 元素(而非 codeWrapper 内),这样它不会随滚动条滚动 + preElement.appendChild(copyButton) + } } // 检查代码块是否已经被处理过 function isBlockProcessed(block: HTMLElement): boolean { - return ( - block.hasAttribute('data-highlighted') || - !!block.querySelector('.line-number') || - !!block.parentElement?.querySelector('.copy-button') - ) + return ( + block.hasAttribute('data-highlighted') || + !!block.querySelector('.line-number') || + !!block.parentElement?.querySelector('.copy-button') + ) } // 标记代码块为已处理 function markBlockAsProcessed(block: HTMLElement) { - block.setAttribute('data-highlighted', 'true') + block.setAttribute('data-highlighted', 'true') } // 处理单个代码块 function processBlock(block: HTMLElement) { - if (isBlockProcessed(block)) { - return - } + if (isBlockProcessed(block)) { + return + } - try { - highlightCode(block) - insertLineNumbers(block) - addCopyButton(block) - markBlockAsProcessed(block) - } catch (error) { - console.warn('处理代码块时出错:', error) - } + try { + highlightCode(block) + insertLineNumbers(block) + addCopyButton(block) + markBlockAsProcessed(block) + } catch (error) { + console.warn('处理代码块时出错:', error) + } } // 查找并处理所有代码块 function processAllCodeBlocks(el: HTMLElement) { - const blocks = Array.from(el.querySelectorAll('pre code')) - const unprocessedBlocks = blocks.filter((block) => !isBlockProcessed(block)) + const blocks = Array.from(el.querySelectorAll('pre code')) + const unprocessedBlocks = blocks.filter((block) => !isBlockProcessed(block)) - if (unprocessedBlocks.length === 0) { - return - } + if (unprocessedBlocks.length === 0) { + return + } - if (unprocessedBlocks.length <= 10) { - // 如果代码块数量少于等于10,直接处理所有代码块 - unprocessedBlocks.forEach((block) => processBlock(block)) - } else { - // 定义每次处理的代码块数 - const batchSize = 10 - let currentIndex = 0 + if (unprocessedBlocks.length <= 10) { + // 如果代码块数量少于等于10,直接处理所有代码块 + unprocessedBlocks.forEach((block) => processBlock(block)) + } else { + // 定义每次处理的代码块数 + const batchSize = 10 + let currentIndex = 0 - const processBatch = () => { - const batch = unprocessedBlocks.slice(currentIndex, currentIndex + batchSize) + const processBatch = () => { + const batch = unprocessedBlocks.slice(currentIndex, currentIndex + batchSize) - batch.forEach((block) => { - processBlock(block) - }) + batch.forEach((block) => { + processBlock(block) + }) - // 更新索引并继续处理下一批 - currentIndex += batchSize - if (currentIndex < unprocessedBlocks.length) { - // 使用 requestAnimationFrame 确保下一帧再处理 - requestAnimationFrame(processBatch) - } - } + // 更新索引并继续处理下一批 + currentIndex += batchSize + if (currentIndex < unprocessedBlocks.length) { + // 使用 requestAnimationFrame 确保下一帧再处理 + requestAnimationFrame(processBatch) + } + } - // 开始处理第一批代码块 - processBatch() - } + // 开始处理第一批代码块 + processBatch() + } } // 重试处理函数 function retryProcessing(el: HTMLElement, maxRetries: number = 3, delay: number = 200) { - let retryCount = 0 + let retryCount = 0 - const tryProcess = () => { - processAllCodeBlocks(el) + const tryProcess = () => { + processAllCodeBlocks(el) - // 检查是否还有未处理的代码块 - const remainingBlocks = Array.from(el.querySelectorAll('pre code')).filter( - (block) => !isBlockProcessed(block) - ) + // 检查是否还有未处理的代码块 + const remainingBlocks = Array.from(el.querySelectorAll('pre code')).filter( + (block) => !isBlockProcessed(block) + ) - if (remainingBlocks.length > 0 && retryCount < maxRetries) { - retryCount++ - setTimeout(tryProcess, delay * retryCount) // 递增延迟 - } - } + if (remainingBlocks.length > 0 && retryCount < maxRetries) { + retryCount++ + setTimeout(tryProcess, delay * retryCount) // 递增延迟 + } + } - tryProcess() + tryProcess() } // 代码高亮、插入行号、复制按钮 const highlightDirective: Directive = { - mounted(el: HTMLElement) { - // 立即尝试处理一次 - processAllCodeBlocks(el) + mounted(el: HTMLElement) { + // 立即尝试处理一次 + processAllCodeBlocks(el) - // 延迟处理,确保 v-html 内容已经渲染 - setTimeout(() => { - retryProcessing(el) - }, 100) + // 延迟处理,确保 v-html 内容已经渲染 + setTimeout(() => { + retryProcessing(el) + }, 100) - // 使用 MutationObserver 监听 DOM 变化 - const observer = new MutationObserver((mutations) => { - let hasNewCodeBlocks = false + // 使用 MutationObserver 监听 DOM 变化 + const observer = new MutationObserver((mutations) => { + let hasNewCodeBlocks = false - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - const element = node as HTMLElement - // 检查新添加的节点是否包含代码块 - if (element.tagName === 'PRE' || element.querySelector('pre code')) { - hasNewCodeBlocks = true - } - } - }) - } - }) + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + const element = node as HTMLElement + // 检查新添加的节点是否包含代码块 + if (element.tagName === 'PRE' || element.querySelector('pre code')) { + hasNewCodeBlocks = true + } + } + }) + } + }) - if (hasNewCodeBlocks) { - // 延迟处理新添加的代码块 - setTimeout(() => { - processAllCodeBlocks(el) - }, 50) - } - }) + if (hasNewCodeBlocks) { + // 延迟处理新添加的代码块 + setTimeout(() => { + processAllCodeBlocks(el) + }, 50) + } + }) - // 开始观察 - observer.observe(el, { - childList: true, - subtree: true - }) + // 开始观察 + observer.observe(el, { + childList: true, + subtree: true + }) - // 将 observer 存储到元素上,以便在 unmounted 时清理 - ;(el as any)._highlightObserver = observer - }, + // 将 observer 存储到元素上,以便在 unmounted 时清理 + ;(el as any)._highlightObserver = observer + }, - updated(el: HTMLElement) { - // 当组件更新时,重新处理代码块 - setTimeout(() => { - processAllCodeBlocks(el) - }, 50) - }, + updated(el: HTMLElement) { + // 当组件更新时,重新处理代码块 + setTimeout(() => { + processAllCodeBlocks(el) + }, 50) + }, - unmounted(el: HTMLElement) { - // 清理 MutationObserver - const observer = (el as any)._highlightObserver - if (observer) { - observer.disconnect() - delete (el as any)._highlightObserver - } - } + unmounted(el: HTMLElement) { + // 清理 MutationObserver + const observer = (el as any)._highlightObserver + if (observer) { + observer.disconnect() + delete (el as any)._highlightObserver + } + } } export function setupHighlightDirective(app: App) { - app.directive('highlight', highlightDirective) + app.directive('highlight', highlightDirective) } diff --git a/src/directives/business/ripple.ts b/src/directives/business/ripple.ts index 8d7d8f9..07ae435 100644 --- a/src/directives/business/ripple.ts +++ b/src/directives/business/ripple.ts @@ -40,75 +40,75 @@ import type { App, Directive, DirectiveBinding } from 'vue' export interface RippleOptions { - /** 水波纹颜色 */ - color?: string + /** 水波纹颜色 */ + color?: string } export const vRipple: Directive = { - mounted(el: HTMLElement, binding: DirectiveBinding) { - // 获取指令的配置参数 - const options: RippleOptions = binding.value || {} + mounted(el: HTMLElement, binding: DirectiveBinding) { + // 获取指令的配置参数 + const options: RippleOptions = binding.value || {} - // 设置元素为相对定位,并隐藏溢出部分 - el.style.position = 'relative' - el.style.overflow = 'hidden' + // 设置元素为相对定位,并隐藏溢出部分 + el.style.position = 'relative' + el.style.overflow = 'hidden' - // 点击事件处理 - el.addEventListener('mousedown', (e: MouseEvent) => { - const rect = el.getBoundingClientRect() - const left = e.clientX - rect.left - const top = e.clientY - rect.top + // 点击事件处理 + el.addEventListener('mousedown', (e: MouseEvent) => { + const rect = el.getBoundingClientRect() + const left = e.clientX - rect.left + const top = e.clientY - rect.top - // 创建水波纹元素 - const ripple = document.createElement('div') - const diameter = Math.max(el.clientWidth, el.clientHeight) - const radius = diameter / 2 + // 创建水波纹元素 + const ripple = document.createElement('div') + const diameter = Math.max(el.clientWidth, el.clientHeight) + const radius = diameter / 2 - // 根据直径计算动画时间(直径越大,动画时间越长) - const baseTime = 600 // 基础动画时间(毫秒) - const scaleFactor = 0.5 // 缩放因子 - const animationDuration = baseTime + diameter * scaleFactor + // 根据直径计算动画时间(直径越大,动画时间越长) + const baseTime = 600 // 基础动画时间(毫秒) + const scaleFactor = 0.5 // 缩放因子 + const animationDuration = baseTime + diameter * scaleFactor - // 设置水波纹的尺寸和位置 - ripple.style.width = ripple.style.height = `${diameter}px` - ripple.style.left = `${left - radius}px` - ripple.style.top = `${top - radius}px` - ripple.style.position = 'absolute' - ripple.style.borderRadius = '50%' - ripple.style.pointerEvents = 'none' + // 设置水波纹的尺寸和位置 + ripple.style.width = ripple.style.height = `${diameter}px` + ripple.style.left = `${left - radius}px` + ripple.style.top = `${top - radius}px` + ripple.style.position = 'absolute' + ripple.style.borderRadius = '50%' + ripple.style.pointerEvents = 'none' - // 判断是否为有色按钮(Element Plus 按钮类型) - const buttonTypes = ['primary', 'info', 'warning', 'danger', 'success'].map( - (type) => `el-button--${type}` - ) - const isColoredButton = buttonTypes.some((type) => el.classList.contains(type)) - const defaultColor = isColoredButton - ? 'rgba(255, 255, 255, 0.25)' // 有色按钮使用白色水波纹 - : 'var(--el-color-primary-light-7)' // 默认按钮使用主题色水波纹 + // 判断是否为有色按钮(Element Plus 按钮类型) + const buttonTypes = ['primary', 'info', 'warning', 'danger', 'success'].map( + (type) => `el-button--${type}` + ) + const isColoredButton = buttonTypes.some((type) => el.classList.contains(type)) + const defaultColor = isColoredButton + ? 'rgba(255, 255, 255, 0.25)' // 有色按钮使用白色水波纹 + : 'var(--el-color-primary-light-7)' // 默认按钮使用主题色水波纹 - // 设置水波纹颜色、初始状态和过渡效果 - ripple.style.backgroundColor = options.color || defaultColor - ripple.style.transform = 'scale(0)' - ripple.style.transition = `transform ${animationDuration}ms cubic-bezier(0.3, 0, 0.2, 1), opacity ${animationDuration}ms cubic-bezier(0.3, 0, 0.5, 1)` - ripple.style.zIndex = '1' + // 设置水波纹颜色、初始状态和过渡效果 + ripple.style.backgroundColor = options.color || defaultColor + ripple.style.transform = 'scale(0)' + ripple.style.transition = `transform ${animationDuration}ms cubic-bezier(0.3, 0, 0.2, 1), opacity ${animationDuration}ms cubic-bezier(0.3, 0, 0.5, 1)` + ripple.style.zIndex = '1' - // 添加水波纹元素到DOM中 - el.appendChild(ripple) + // 添加水波纹元素到DOM中 + el.appendChild(ripple) - // 触发动画 - requestAnimationFrame(() => { - ripple.style.transform = 'scale(2)' - ripple.style.opacity = '0' - }) + // 触发动画 + requestAnimationFrame(() => { + ripple.style.transform = 'scale(2)' + ripple.style.opacity = '0' + }) - // 动画结束后移除水波纹元素 - setTimeout(() => { - ripple.remove() - }, animationDuration + 500) // 增加500ms缓冲时间 - }) - } + // 动画结束后移除水波纹元素 + setTimeout(() => { + ripple.remove() + }, animationDuration + 500) // 增加500ms缓冲时间 + }) + } } export function setupRippleDirective(app: App) { - app.directive('ripple', vRipple) + app.directive('ripple', vRipple) } diff --git a/src/directives/core/auth.ts b/src/directives/core/auth.ts index b9e85d8..0fac2b8 100644 --- a/src/directives/core/auth.ts +++ b/src/directives/core/auth.ts @@ -36,33 +36,33 @@ import { router } from '@/router' import { App, Directive, DirectiveBinding } from 'vue' interface AuthBinding extends DirectiveBinding { - value: string + value: string } function checkAuthPermission(el: HTMLElement, binding: AuthBinding): void { - // 获取当前路由的权限列表 - const authList = (router.currentRoute.value.meta.authList as Array<{ authMark: string }>) || [] + // 获取当前路由的权限列表 + const authList = (router.currentRoute.value.meta.authList as Array<{ authMark: string }>) || [] - // 检查是否有对应的权限标识 - const hasPermission = authList.some((item) => item.authMark === binding.value) + // 检查是否有对应的权限标识 + const hasPermission = authList.some((item) => item.authMark === binding.value) - // 如果没有权限,移除元素 - if (!hasPermission) { - removeElement(el) - } + // 如果没有权限,移除元素 + if (!hasPermission) { + removeElement(el) + } } function removeElement(el: HTMLElement): void { - if (el.parentNode) { - el.parentNode.removeChild(el) - } + if (el.parentNode) { + el.parentNode.removeChild(el) + } } const authDirective: Directive = { - mounted: checkAuthPermission, - updated: checkAuthPermission + mounted: checkAuthPermission, + updated: checkAuthPermission } export function setupAuthDirective(app: App): void { - app.directive('auth', authDirective) + app.directive('auth', authDirective) } diff --git a/src/directives/core/roles.ts b/src/directives/core/roles.ts index 2ab1029..b0a076e 100644 --- a/src/directives/core/roles.ts +++ b/src/directives/core/roles.ts @@ -48,42 +48,42 @@ import { useUserStore } from '@/store/modules/user' import { App, Directive, DirectiveBinding } from 'vue' interface RolesBinding extends DirectiveBinding { - value: string | string[] + value: string | string[] } function checkRolePermission(el: HTMLElement, binding: RolesBinding): void { - const userStore = useUserStore() - const userRoles = userStore.getUserInfo.roles + const userStore = useUserStore() + const userRoles = userStore.getUserInfo.roles - // 如果用户角色为空或未定义,移除元素 - if (!userRoles?.length) { - removeElement(el) - return - } + // 如果用户角色为空或未定义,移除元素 + if (!userRoles?.length) { + removeElement(el) + return + } - // 确保指令值为数组格式 - const requiredRoles = Array.isArray(binding.value) ? binding.value : [binding.value] + // 确保指令值为数组格式 + const requiredRoles = Array.isArray(binding.value) ? binding.value : [binding.value] - // 检查用户是否具有所需角色之一 - const hasPermission = requiredRoles.some((role: string) => userRoles.includes(role)) + // 检查用户是否具有所需角色之一 + const hasPermission = requiredRoles.some((role: string) => userRoles.includes(role)) - // 如果没有权限,安全地移除元素 - if (!hasPermission) { - removeElement(el) - } + // 如果没有权限,安全地移除元素 + if (!hasPermission) { + removeElement(el) + } } function removeElement(el: HTMLElement): void { - if (el.parentNode) { - el.parentNode.removeChild(el) - } + if (el.parentNode) { + el.parentNode.removeChild(el) + } } const rolesDirective: Directive = { - mounted: checkRolePermission, - updated: checkRolePermission + mounted: checkRolePermission, + updated: checkRolePermission } export function setupRolesDirective(app: App): void { - app.directive('roles', rolesDirective) + app.directive('roles', rolesDirective) } diff --git a/src/directives/index.ts b/src/directives/index.ts index 780464b..4540fcb 100644 --- a/src/directives/index.ts +++ b/src/directives/index.ts @@ -5,8 +5,8 @@ import { setupRippleDirective } from './business/ripple' import { setupRolesDirective } from './core/roles' export function setupGlobDirectives(app: App) { - setupAuthDirective(app) // 权限指令 - setupRolesDirective(app) // 角色权限指令 - setupHighlightDirective(app) // 高亮指令 - setupRippleDirective(app) // 水波纹指令 + setupAuthDirective(app) // 权限指令 + setupRolesDirective(app) // 角色权限指令 + setupHighlightDirective(app) // 高亮指令 + setupRippleDirective(app) // 水波纹指令 } diff --git a/src/enums/appEnum.ts b/src/enums/appEnum.ts index a39c278..4444d28 100644 --- a/src/enums/appEnum.ts +++ b/src/enums/appEnum.ts @@ -18,64 +18,64 @@ * 菜单类型 */ export enum MenuTypeEnum { - /** 左侧菜单 */ - LEFT = 'left', - /** 顶部菜单 */ - TOP = 'top', - /** 顶部+左侧菜单 */ - TOP_LEFT = 'top-left', - /** 双栏菜单 */ - DUAL_MENU = 'dual-menu' + /** 左侧菜单 */ + LEFT = 'left', + /** 顶部菜单 */ + TOP = 'top', + /** 顶部+左侧菜单 */ + TOP_LEFT = 'top-left', + /** 双栏菜单 */ + DUAL_MENU = 'dual-menu' } /** * 系统主题 */ export enum SystemThemeEnum { - /** 暗色主题 */ - DARK = 'dark', - /** 亮色主题 */ - LIGHT = 'light', - /** 自动主题(跟随系统) */ - AUTO = 'auto' + /** 暗色主题 */ + DARK = 'dark', + /** 亮色主题 */ + LIGHT = 'light', + /** 自动主题(跟随系统) */ + AUTO = 'auto' } /** * 菜单主题 */ export enum MenuThemeEnum { - /** 暗色主题 */ - DARK = 'dark', - /** 亮色主题 */ - LIGHT = 'light', - /** 设计主题 */ - DESIGN = 'design' + /** 暗色主题 */ + DARK = 'dark', + /** 亮色主题 */ + LIGHT = 'light', + /** 设计主题 */ + DESIGN = 'design' } /** * 菜单宽度 */ export enum MenuWidth { - /** 收起宽度 */ - CLOSE = '64px' + /** 收起宽度 */ + CLOSE = '64px' } /** * 语言类型 */ export enum LanguageEnum { - /** 中文 */ - ZH = 'zh', - /** 英文 */ - EN = 'en' + /** 中文 */ + ZH = 'zh', + /** 英文 */ + EN = 'en' } /** * 容器宽度 */ export enum ContainerWidthEnum { - /** 全屏宽度 */ - FULL = '100%', - /** 固定宽度 */ - BOXED = '1200px' + /** 全屏宽度 */ + FULL = '100%', + /** 固定宽度 */ + BOXED = '1200px' } diff --git a/src/enums/formEnum.ts b/src/enums/formEnum.ts index 8e9b3b4..dfabe70 100644 --- a/src/enums/formEnum.ts +++ b/src/enums/formEnum.ts @@ -12,13 +12,13 @@ // 页面类型 export enum PageModeEnum { - Add, // 新增 - Edit // 编辑 + Add, // 新增 + Edit // 编辑 } // 表格大小 export enum TableSizeEnum { - DEFAULT = 'default', - SMALL = 'small', - LARGE = 'large' + DEFAULT = 'default', + SMALL = 'small', + LARGE = 'large' } diff --git a/src/env.d.ts b/src/env.d.ts index 4401f21..80abd08 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -9,25 +9,25 @@ declare module 'vue-img-cutter' declare module 'file-saver' declare module 'qrcode.vue' { - export type Level = 'L' | 'M' | 'Q' | 'H' - export type RenderAs = 'canvas' | 'svg' - export type GradientType = 'linear' | 'radial' - export interface ImageSettings { - src: string - height: number - width: number - excavate: boolean - } - export interface QRCodeProps { - value: string - size?: number - level?: Level - background?: string - foreground?: string - renderAs?: RenderAs - } - const QrcodeVue: any - export default QrcodeVue + export type Level = 'L' | 'M' | 'Q' | 'H' + export type RenderAs = 'canvas' | 'svg' + export type GradientType = 'linear' | 'radial' + export interface ImageSettings { + src: string + height: number + width: number + excavate: boolean + } + export interface QRCodeProps { + value: string + size?: number + level?: Level + background?: string + foreground?: string + renderAs?: RenderAs + } + const QrcodeVue: any + export default QrcodeVue } // 全局变量声明 diff --git a/src/hooks/core/useAppMode.ts b/src/hooks/core/useAppMode.ts index c39cd9e..334a61e 100644 --- a/src/hooks/core/useAppMode.ts +++ b/src/hooks/core/useAppMode.ts @@ -18,28 +18,28 @@ import { computed } from 'vue' export function useAppMode() { - // 获取访问模式配置 - const accessMode = import.meta.env.VITE_ACCESS_MODE + // 获取访问模式配置 + const accessMode = import.meta.env.VITE_ACCESS_MODE - /** - * 是否为前端控制模式 - * 前端模式:权限由前端路由配置控制 - */ - const isFrontendMode = computed(() => accessMode === 'frontend') - /** - * 是否为后端控制模式 - * 后端模式:权限由后端接口返回的菜单数据控制 - */ - const isBackendMode = computed(() => accessMode === 'backend') + /** + * 是否为前端控制模式 + * 前端模式:权限由前端路由配置控制 + */ + const isFrontendMode = computed(() => accessMode === 'frontend') + /** + * 是否为后端控制模式 + * 后端模式:权限由后端接口返回的菜单数据控制 + */ + const isBackendMode = computed(() => accessMode === 'backend') - /** - * 当前应用模式 - */ - const currentMode = computed(() => accessMode) + /** + * 当前应用模式 + */ + const currentMode = computed(() => accessMode) - return { - isFrontendMode, - isBackendMode, - currentMode - } + return { + isFrontendMode, + isBackendMode, + currentMode + } } diff --git a/src/hooks/core/useAuth.ts b/src/hooks/core/useAuth.ts index 283b859..5b00b24 100644 --- a/src/hooks/core/useAuth.ts +++ b/src/hooks/core/useAuth.ts @@ -41,34 +41,34 @@ type AuthItem = NonNullable[number] const userStore = useUserStore() export const useAuth = () => { - const route = useRoute() - const { isFrontendMode } = useAppMode() - const { info } = storeToRefs(userStore) + const route = useRoute() + const { isFrontendMode } = useAppMode() + const { info } = storeToRefs(userStore) - // 前端按钮权限(例如:['add', 'edit']) - const frontendAuthList = info.value?.buttons ?? [] + // 前端按钮权限(例如:['add', 'edit']) + const frontendAuthList = info.value?.buttons ?? [] - // 后端路由 meta 配置的权限列表(例如:[{ authMark: 'add' }]) - const backendAuthList: AuthItem[] = Array.isArray(route.meta.authList) - ? (route.meta.authList as AuthItem[]) - : [] + // 后端路由 meta 配置的权限列表(例如:[{ authMark: 'add' }]) + const backendAuthList: AuthItem[] = Array.isArray(route.meta.authList) + ? (route.meta.authList as AuthItem[]) + : [] - /** - * 检查是否拥有某权限标识(前后端模式通用) - * @param auth 权限标识 - * @returns 是否有权限 - */ - const hasAuth = (auth: string): boolean => { - // 前端模式 - if (isFrontendMode.value) { - return frontendAuthList.includes(auth) - } + /** + * 检查是否拥有某权限标识(前后端模式通用) + * @param auth 权限标识 + * @returns 是否有权限 + */ + const hasAuth = (auth: string): boolean => { + // 前端模式 + if (isFrontendMode.value) { + return frontendAuthList.includes(auth) + } - // 后端模式 - return backendAuthList.some((item) => item?.authMark === auth) - } + // 后端模式 + return backendAuthList.some((item) => item?.authMark === auth) + } - return { - hasAuth - } + return { + hasAuth + } } diff --git a/src/hooks/core/useCeremony.ts b/src/hooks/core/useCeremony.ts index ead2630..e9c41dc 100644 --- a/src/hooks/core/useCeremony.ts +++ b/src/hooks/core/useCeremony.ts @@ -51,14 +51,14 @@ import { festivalConfigList } from '@/config/modules/festival' * 节日庆祝配置常量 */ const FESTIVAL_CONFIG = { - /** 初始延迟(毫秒) */ - INITIAL_DELAY: 300, - /** 烟花播放间隔(毫秒) */ - FIREWORK_INTERVAL: 1000, - /** 文本显示延迟(毫秒) */ - TEXT_DELAY: 2000, - /** 默认烟花播放次数 */ - DEFAULT_FIREWORKS_COUNT: 3 + /** 初始延迟(毫秒) */ + INITIAL_DELAY: 300, + /** 烟花播放间隔(毫秒) */ + FIREWORK_INTERVAL: 1000, + /** 文本显示延迟(毫秒) */ + TEXT_DELAY: 2000, + /** 默认烟花播放次数 */ + DEFAULT_FIREWORKS_COUNT: 3 } as const /** @@ -66,119 +66,121 @@ const FESTIVAL_CONFIG = { * 提供节日烟花效果和祝福文本展示 */ export function useCeremony() { - const settingStore = useSettingStore() - const { holidayFireworksLoaded, isShowFireworks } = storeToRefs(settingStore) + const settingStore = useSettingStore() + const { holidayFireworksLoaded, isShowFireworks } = storeToRefs(settingStore) - let fireworksInterval: { pause: () => void } | null = null + let fireworksInterval: { pause: () => void } | null = null - /** - * 检查日期是否在节日范围内 - * @param currentDate 当前日期 - * @param festivalDate 节日开始日期 - * @param festivalEndDate 节日结束日期(可选) - */ - const isDateInRange = ( - currentDate: string, - festivalDate: string, - festivalEndDate?: string - ): boolean => { - if (!festivalEndDate) { - // 单日节日 - return currentDate === festivalDate - } + /** + * 检查日期是否在节日范围内 + * @param currentDate 当前日期 + * @param festivalDate 节日开始日期 + * @param festivalEndDate 节日结束日期(可选) + */ + const isDateInRange = ( + currentDate: string, + festivalDate: string, + festivalEndDate?: string + ): boolean => { + if (!festivalEndDate) { + // 单日节日 + return currentDate === festivalDate + } - // 跨日期节日 - const current = new Date(currentDate) - const start = new Date(festivalDate) - const end = new Date(festivalEndDate) + // 跨日期节日 + const current = new Date(currentDate) + const start = new Date(festivalDate) + const end = new Date(festivalEndDate) - return current >= start && current <= end - } + return current >= start && current <= end + } - /** - * 获取当前日期对应的节日数据 - */ - const currentFestivalData = computed(() => { - const currentDate = useDateFormat(new Date(), 'YYYY-MM-DD').value - return festivalConfigList.find((item) => isDateInRange(currentDate, item.date, item.endDate)) - }) + /** + * 获取当前日期对应的节日数据 + */ + const currentFestivalData = computed(() => { + const currentDate = useDateFormat(new Date(), 'YYYY-MM-DD').value + return festivalConfigList.find((item) => + isDateInRange(currentDate, item.date, item.endDate) + ) + }) - /** - * 更新节日日期到 store - */ - const updateFestivalDate = () => { - settingStore.setFestivalDate(currentFestivalData.value?.date || '') - } + /** + * 更新节日日期到 store + */ + const updateFestivalDate = () => { + settingStore.setFestivalDate(currentFestivalData.value?.date || '') + } - /** - * 触发烟花效果 - */ - const triggerFirework = () => { - mittBus.emit('triggerFireworks', currentFestivalData.value?.image) - } + /** + * 触发烟花效果 + */ + const triggerFirework = () => { + mittBus.emit('triggerFireworks', currentFestivalData.value?.image) + } - /** - * 完成烟花效果后显示文本 - */ - const showFestivalText = () => { - settingStore.setholidayFireworksLoaded(true) + /** + * 完成烟花效果后显示文本 + */ + const showFestivalText = () => { + settingStore.setholidayFireworksLoaded(true) - useTimeoutFn(() => { - settingStore.setShowFestivalText(true) - updateFestivalDate() - }, FESTIVAL_CONFIG.TEXT_DELAY) - } + useTimeoutFn(() => { + settingStore.setShowFestivalText(true) + updateFestivalDate() + }, FESTIVAL_CONFIG.TEXT_DELAY) + } - /** - * 启动烟花循环 - */ - const startFireworksLoop = () => { - let playedCount = 0 - // 使用节日配置的播放次数,如果没有则使用默认值 - const count = currentFestivalData.value?.count ?? FESTIVAL_CONFIG.DEFAULT_FIREWORKS_COUNT + /** + * 启动烟花循环 + */ + const startFireworksLoop = () => { + let playedCount = 0 + // 使用节日配置的播放次数,如果没有则使用默认值 + const count = currentFestivalData.value?.count ?? FESTIVAL_CONFIG.DEFAULT_FIREWORKS_COUNT - const { pause } = useIntervalFn(() => { - triggerFirework() - playedCount++ + const { pause } = useIntervalFn(() => { + triggerFirework() + playedCount++ - if (playedCount >= count) { - pause() - showFestivalText() - } - }, FESTIVAL_CONFIG.FIREWORK_INTERVAL) + if (playedCount >= count) { + pause() + showFestivalText() + } + }, FESTIVAL_CONFIG.FIREWORK_INTERVAL) - fireworksInterval = { pause } - } + fireworksInterval = { pause } + } - /** - * 开启节日庆祝 - */ - const openFestival = () => { - if (!currentFestivalData.value || !isShowFireworks.value) { - return - } + /** + * 开启节日庆祝 + */ + const openFestival = () => { + if (!currentFestivalData.value || !isShowFireworks.value) { + return + } - const { start } = useTimeoutFn(startFireworksLoop, FESTIVAL_CONFIG.INITIAL_DELAY) - start() - } + const { start } = useTimeoutFn(startFireworksLoop, FESTIVAL_CONFIG.INITIAL_DELAY) + start() + } - /** - * 清理烟花效果 - */ - const cleanup = () => { - if (fireworksInterval) { - fireworksInterval.pause() - fireworksInterval = null - } - settingStore.setShowFestivalText(false) - updateFestivalDate() - } + /** + * 清理烟花效果 + */ + const cleanup = () => { + if (fireworksInterval) { + fireworksInterval.pause() + fireworksInterval = null + } + settingStore.setShowFestivalText(false) + updateFestivalDate() + } - return { - openFestival, - cleanup, - holidayFireworksLoaded, - currentFestivalData, - isShowFireworks - } + return { + openFestival, + cleanup, + holidayFireworksLoaded, + currentFestivalData, + isShowFireworks + } } diff --git a/src/hooks/core/useChart.ts b/src/hooks/core/useChart.ts index 29ba1d1..dd10813 100644 --- a/src/hooks/core/useChart.ts +++ b/src/hooks/core/useChart.ts @@ -57,24 +57,24 @@ import type { BaseChartProps, ChartThemeConfig, UseChartOptions } from '@/types/ // 图表主题配置 export const useChartOps = (): ChartThemeConfig => ({ - /** */ - chartHeight: '16rem', - /** 字体大小 */ - fontSize: 13, - /** 字体颜色 */ - fontColor: '#999', - /** 主题颜色 */ - themeColor: getCssVar('--el-color-primary-light-1'), - /** 颜色组 */ - colors: [ - getCssVar('--el-color-primary-light-1'), - '#4ABEFF', - '#EDF2FF', - '#14DEBA', - '#FFAF20', - '#FA8A6C', - '#FFAF20' - ] + /** */ + chartHeight: '16rem', + /** 字体大小 */ + fontSize: 13, + /** 字体颜色 */ + fontColor: '#999', + /** 主题颜色 */ + themeColor: getCssVar('--el-color-primary-light-1'), + /** 颜色组 */ + colors: [ + getCssVar('--el-color-primary-light-1'), + '#4ABEFF', + '#EDF2FF', + '#14DEBA', + '#FFAF20', + '#FA8A6C', + '#FFAF20' + ] }) // 常量定义 @@ -83,377 +83,377 @@ const MENU_RESIZE_DELAYS = [50, 100, 200] as const const RESIZE_DEBOUNCE_DELAY = 100 export function useChart(options: UseChartOptions = {}) { - const { initOptions, initDelay = 0, threshold = 0.1, autoTheme = true } = options + const { initOptions, initDelay = 0, threshold = 0.1, autoTheme = true } = options - const settingStore = useSettingStore() - const { isDark, menuOpen, menuType } = storeToRefs(settingStore) + const settingStore = useSettingStore() + const { isDark, menuOpen, menuType } = storeToRefs(settingStore) - const chartRef = ref() - let chart: echarts.ECharts | null = null - let intersectionObserver: IntersectionObserver | null = null - let pendingOptions: EChartsOption | null = null - let resizeTimeoutId: number | null = null - let resizeFrameId: number | null = null - let isDestroyed = false - let emptyStateDiv: HTMLElement | null = null + const chartRef = ref() + let chart: echarts.ECharts | null = null + let intersectionObserver: IntersectionObserver | null = null + let pendingOptions: EChartsOption | null = null + let resizeTimeoutId: number | null = null + let resizeFrameId: number | null = null + let isDestroyed = false + let emptyStateDiv: HTMLElement | null = null - // 清理定时器的统一方法 - const clearTimers = () => { - if (resizeTimeoutId) { - clearTimeout(resizeTimeoutId) - resizeTimeoutId = null - } - if (resizeFrameId) { - cancelAnimationFrame(resizeFrameId) - resizeFrameId = null - } - } + // 清理定时器的统一方法 + const clearTimers = () => { + if (resizeTimeoutId) { + clearTimeout(resizeTimeoutId) + resizeTimeoutId = null + } + if (resizeFrameId) { + cancelAnimationFrame(resizeFrameId) + resizeFrameId = null + } + } - // 使用 requestAnimationFrame 优化 resize 处理 - const requestAnimationResize = () => { - if (resizeFrameId) { - cancelAnimationFrame(resizeFrameId) - } - resizeFrameId = requestAnimationFrame(() => { - handleResize() - resizeFrameId = null - }) - } + // 使用 requestAnimationFrame 优化 resize 处理 + const requestAnimationResize = () => { + if (resizeFrameId) { + cancelAnimationFrame(resizeFrameId) + } + resizeFrameId = requestAnimationFrame(() => { + handleResize() + resizeFrameId = null + }) + } - // 防抖的resize处理(用于窗口resize事件) - const debouncedResize = () => { - if (resizeTimeoutId) { - clearTimeout(resizeTimeoutId) - } - resizeTimeoutId = window.setTimeout(() => { - requestAnimationResize() - resizeTimeoutId = null - }, RESIZE_DEBOUNCE_DELAY) - } + // 防抖的resize处理(用于窗口resize事件) + const debouncedResize = () => { + if (resizeTimeoutId) { + clearTimeout(resizeTimeoutId) + } + resizeTimeoutId = window.setTimeout(() => { + requestAnimationResize() + resizeTimeoutId = null + }, RESIZE_DEBOUNCE_DELAY) + } - // 多延迟resize处理 - 统一方法 - const multiDelayResize = (delays: readonly number[]) => { - // 立即调用一次,快速响应 - nextTick(requestAnimationResize) + // 多延迟resize处理 - 统一方法 + const multiDelayResize = (delays: readonly number[]) => { + // 立即调用一次,快速响应 + nextTick(requestAnimationResize) - // 使用延迟时间,确保图表正确适应变化 - delays.forEach((delay) => { - setTimeout(requestAnimationResize, delay) - }) - } + // 使用延迟时间,确保图表正确适应变化 + delays.forEach((delay) => { + setTimeout(requestAnimationResize, delay) + }) + } - // 收缩菜单时,重新计算图表大小(仅在图表存在时监听) - let menuOpenStopHandle: (() => void) | null = null - let menuTypeStopHandle: (() => void) | null = null + // 收缩菜单时,重新计算图表大小(仅在图表存在时监听) + let menuOpenStopHandle: (() => void) | null = null + let menuTypeStopHandle: (() => void) | null = null - const setupMenuWatchers = () => { - menuOpenStopHandle = watch(menuOpen, () => multiDelayResize(RESIZE_DELAYS)) - menuTypeStopHandle = watch(menuType, () => { - nextTick(requestAnimationResize) - setTimeout(() => multiDelayResize(MENU_RESIZE_DELAYS), 0) - }) - } + const setupMenuWatchers = () => { + menuOpenStopHandle = watch(menuOpen, () => multiDelayResize(RESIZE_DELAYS)) + menuTypeStopHandle = watch(menuType, () => { + nextTick(requestAnimationResize) + setTimeout(() => multiDelayResize(MENU_RESIZE_DELAYS), 0) + }) + } - const cleanupMenuWatchers = () => { - menuOpenStopHandle?.() - menuTypeStopHandle?.() - menuOpenStopHandle = null - menuTypeStopHandle = null - } + const cleanupMenuWatchers = () => { + menuOpenStopHandle?.() + menuTypeStopHandle?.() + menuOpenStopHandle = null + menuTypeStopHandle = null + } - // 主题变化时重新设置图表选项 - let themeStopHandle: (() => void) | null = null + // 主题变化时重新设置图表选项 + let themeStopHandle: (() => void) | null = null - const setupThemeWatcher = () => { - if (autoTheme) { - themeStopHandle = watch(isDark, () => { - // 更新空状态样式 - emptyStateManager.updateStyle() + const setupThemeWatcher = () => { + if (autoTheme) { + themeStopHandle = watch(isDark, () => { + // 更新空状态样式 + emptyStateManager.updateStyle() - if (chart && !isDestroyed) { - // 使用 requestAnimationFrame 优化主题更新 - requestAnimationFrame(() => { - if (chart && !isDestroyed) { - const currentOptions = chart.getOption() - if (currentOptions) { - updateChart(currentOptions as EChartsOption) - } - } - }) - } - }) - } - } + if (chart && !isDestroyed) { + // 使用 requestAnimationFrame 优化主题更新 + requestAnimationFrame(() => { + if (chart && !isDestroyed) { + const currentOptions = chart.getOption() + if (currentOptions) { + updateChart(currentOptions as EChartsOption) + } + } + }) + } + }) + } + } - const cleanupThemeWatcher = () => { - themeStopHandle?.() - themeStopHandle = null - } + const cleanupThemeWatcher = () => { + themeStopHandle?.() + themeStopHandle = null + } - // 样式生成器 - 统一的样式配置 - const createLineStyle = (color: string, width = 1, type?: 'solid' | 'dashed') => ({ - color, - width, - ...(type && { type }) - }) + // 样式生成器 - 统一的样式配置 + const createLineStyle = (color: string, width = 1, type?: 'solid' | 'dashed') => ({ + color, + width, + ...(type && { type }) + }) - // 缓存样式配置以减少重复计算 - const styleCache = { - axisLine: null as any, - splitLine: null as any, - axisLabel: null as any, - lastDarkValue: isDark.value - } + // 缓存样式配置以减少重复计算 + const styleCache = { + axisLine: null as any, + splitLine: null as any, + axisLabel: null as any, + lastDarkValue: isDark.value + } - const clearStyleCache = () => { - styleCache.axisLine = null - styleCache.splitLine = null - styleCache.axisLabel = null - styleCache.lastDarkValue = isDark.value - } + const clearStyleCache = () => { + styleCache.axisLine = null + styleCache.splitLine = null + styleCache.axisLabel = null + styleCache.lastDarkValue = isDark.value + } - // 坐标轴线样式 - const getAxisLineStyle = (show: boolean = true) => { - if (styleCache.lastDarkValue !== isDark.value) { - clearStyleCache() - } - if (!styleCache.axisLine) { - styleCache.axisLine = { - show, - lineStyle: createLineStyle(isDark.value ? '#444' : '#EDEDED') - } - } - return styleCache.axisLine - } + // 坐标轴线样式 + const getAxisLineStyle = (show: boolean = true) => { + if (styleCache.lastDarkValue !== isDark.value) { + clearStyleCache() + } + if (!styleCache.axisLine) { + styleCache.axisLine = { + show, + lineStyle: createLineStyle(isDark.value ? '#444' : '#EDEDED') + } + } + return styleCache.axisLine + } - // 分割线样式 - const getSplitLineStyle = (show: boolean = true) => { - if (styleCache.lastDarkValue !== isDark.value) { - clearStyleCache() - } - if (!styleCache.splitLine) { - styleCache.splitLine = { - show, - lineStyle: createLineStyle(isDark.value ? '#444' : '#EDEDED', 1, 'dashed') - } - } - return styleCache.splitLine - } + // 分割线样式 + const getSplitLineStyle = (show: boolean = true) => { + if (styleCache.lastDarkValue !== isDark.value) { + clearStyleCache() + } + if (!styleCache.splitLine) { + styleCache.splitLine = { + show, + lineStyle: createLineStyle(isDark.value ? '#444' : '#EDEDED', 1, 'dashed') + } + } + return styleCache.splitLine + } - // 坐标轴标签样式 - const getAxisLabelStyle = (show: boolean = true) => { - if (styleCache.lastDarkValue !== isDark.value) { - clearStyleCache() - } - if (!styleCache.axisLabel) { - const { fontColor, fontSize } = useChartOps() - styleCache.axisLabel = { - show, - color: fontColor, - fontSize - } - } - return styleCache.axisLabel - } + // 坐标轴标签样式 + const getAxisLabelStyle = (show: boolean = true) => { + if (styleCache.lastDarkValue !== isDark.value) { + clearStyleCache() + } + if (!styleCache.axisLabel) { + const { fontColor, fontSize } = useChartOps() + styleCache.axisLabel = { + show, + color: fontColor, + fontSize + } + } + return styleCache.axisLabel + } - // 坐标轴刻度样式(静态配置,无需缓存) - const getAxisTickStyle = () => ({ - show: false - }) + // 坐标轴刻度样式(静态配置,无需缓存) + const getAxisTickStyle = () => ({ + show: false + }) - // 获取动画配置 - const getAnimationConfig = (animationDelay: number = 50, animationDuration: number = 1500) => ({ - animationDelay: (idx: number) => idx * animationDelay + 200, - animationDuration: (idx: number) => animationDuration - idx * 50, - animationEasing: 'quarticOut' as const - }) + // 获取动画配置 + const getAnimationConfig = (animationDelay: number = 50, animationDuration: number = 1500) => ({ + animationDelay: (idx: number) => idx * animationDelay + 200, + animationDuration: (idx: number) => animationDuration - idx * 50, + animationEasing: 'quarticOut' as const + }) - // 获取统一的 tooltip 配置 - const getTooltipStyle = (trigger: 'item' | 'axis' = 'axis', customOptions: any = {}) => ({ - trigger, - backgroundColor: isDark.value ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.9)', - borderColor: isDark.value ? '#333' : '#ddd', - borderWidth: 1, - textStyle: { - color: isDark.value ? '#fff' : '#333' - }, - ...customOptions - }) + // 获取统一的 tooltip 配置 + const getTooltipStyle = (trigger: 'item' | 'axis' = 'axis', customOptions: any = {}) => ({ + trigger, + backgroundColor: isDark.value ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.9)', + borderColor: isDark.value ? '#333' : '#ddd', + borderWidth: 1, + textStyle: { + color: isDark.value ? '#fff' : '#333' + }, + ...customOptions + }) - // 获取统一的图例配置 - const getLegendStyle = ( - position: 'bottom' | 'top' | 'left' | 'right' = 'bottom', - customOptions: any = {} - ) => { - const baseConfig = { - textStyle: { - color: isDark.value ? '#fff' : '#333' - }, - itemWidth: 12, - itemHeight: 12, - itemGap: 20, - ...customOptions - } + // 获取统一的图例配置 + const getLegendStyle = ( + position: 'bottom' | 'top' | 'left' | 'right' = 'bottom', + customOptions: any = {} + ) => { + const baseConfig = { + textStyle: { + color: isDark.value ? '#fff' : '#333' + }, + itemWidth: 12, + itemHeight: 12, + itemGap: 20, + ...customOptions + } - // 根据位置设置不同的配置 - switch (position) { - case 'bottom': - return { - ...baseConfig, - bottom: 0, - left: 'center', - orient: 'horizontal', - icon: 'roundRect' - } - case 'top': - return { - ...baseConfig, - top: 0, - left: 'center', - orient: 'horizontal', - icon: 'roundRect' - } - case 'left': - return { - ...baseConfig, - left: 0, - top: 'center', - orient: 'vertical', - icon: 'roundRect' - } - case 'right': - return { - ...baseConfig, - right: 0, - top: 'center', - orient: 'vertical', - icon: 'roundRect' - } - default: - return baseConfig - } - } + // 根据位置设置不同的配置 + switch (position) { + case 'bottom': + return { + ...baseConfig, + bottom: 0, + left: 'center', + orient: 'horizontal', + icon: 'roundRect' + } + case 'top': + return { + ...baseConfig, + top: 0, + left: 'center', + orient: 'horizontal', + icon: 'roundRect' + } + case 'left': + return { + ...baseConfig, + left: 0, + top: 'center', + orient: 'vertical', + icon: 'roundRect' + } + case 'right': + return { + ...baseConfig, + right: 0, + top: 'center', + orient: 'vertical', + icon: 'roundRect' + } + default: + return baseConfig + } + } - // 根据图例位置计算 grid 配置 - const getGridWithLegend = ( - showLegend: boolean, - legendPosition: 'bottom' | 'top' | 'left' | 'right' = 'bottom', - baseGrid: any = {} - ) => { - const defaultGrid = { - top: 15, - right: 15, - bottom: 8, - left: 0, - containLabel: true, - ...baseGrid - } + // 根据图例位置计算 grid 配置 + const getGridWithLegend = ( + showLegend: boolean, + legendPosition: 'bottom' | 'top' | 'left' | 'right' = 'bottom', + baseGrid: any = {} + ) => { + const defaultGrid = { + top: 15, + right: 15, + bottom: 8, + left: 0, + containLabel: true, + ...baseGrid + } - if (!showLegend) { - return defaultGrid - } + if (!showLegend) { + return defaultGrid + } - // 根据图例位置调整 grid - switch (legendPosition) { - case 'bottom': - return { - ...defaultGrid, - bottom: 40 - } - case 'top': - return { - ...defaultGrid, - top: 40 - } - case 'left': - return { - ...defaultGrid, - left: 120 - } - case 'right': - return { - ...defaultGrid, - right: 120 - } - default: - return defaultGrid - } - } + // 根据图例位置调整 grid + switch (legendPosition) { + case 'bottom': + return { + ...defaultGrid, + bottom: 40 + } + case 'top': + return { + ...defaultGrid, + top: 40 + } + case 'left': + return { + ...defaultGrid, + left: 120 + } + case 'right': + return { + ...defaultGrid, + right: 120 + } + default: + return defaultGrid + } + } - // 创建IntersectionObserver - const createIntersectionObserver = () => { - if (intersectionObserver || !chartRef.value) return + // 创建IntersectionObserver + const createIntersectionObserver = () => { + if (intersectionObserver || !chartRef.value) return - intersectionObserver = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting && pendingOptions && !isDestroyed) { - // 使用 requestAnimationFrame 确保在下一帧初始化图表 - requestAnimationFrame(() => { - if (!isDestroyed && pendingOptions) { - try { - // 元素变为可见,初始化图表 - if (!chart) { - chart = echarts.init(entry.target as HTMLElement) - } + intersectionObserver = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && pendingOptions && !isDestroyed) { + // 使用 requestAnimationFrame 确保在下一帧初始化图表 + requestAnimationFrame(() => { + if (!isDestroyed && pendingOptions) { + try { + // 元素变为可见,初始化图表 + if (!chart) { + chart = echarts.init(entry.target as HTMLElement) + } - // 触发自定义事件,让组件处理动画逻辑 - const event = new CustomEvent('chartVisible', { - detail: { options: pendingOptions } - }) - entry.target.dispatchEvent(event) + // 触发自定义事件,让组件处理动画逻辑 + const event = new CustomEvent('chartVisible', { + detail: { options: pendingOptions } + }) + entry.target.dispatchEvent(event) - pendingOptions = null - cleanupIntersectionObserver() - } catch (error) { - console.error('图表初始化失败:', error) - } - } - }) - } - }) - }, - { threshold } - ) + pendingOptions = null + cleanupIntersectionObserver() + } catch (error) { + console.error('图表初始化失败:', error) + } + } + }) + } + }) + }, + { threshold } + ) - intersectionObserver.observe(chartRef.value) - } + intersectionObserver.observe(chartRef.value) + } - // 清理IntersectionObserver - const cleanupIntersectionObserver = () => { - if (intersectionObserver) { - intersectionObserver.disconnect() - intersectionObserver = null - } - } + // 清理IntersectionObserver + const cleanupIntersectionObserver = () => { + if (intersectionObserver) { + intersectionObserver.disconnect() + intersectionObserver = null + } + } - // 检查容器是否可见 - const isContainerVisible = (element: HTMLElement): boolean => { - const rect = element.getBoundingClientRect() - return rect.width > 0 && rect.height > 0 && rect.top < window.innerHeight && rect.bottom > 0 - } + // 检查容器是否可见 + const isContainerVisible = (element: HTMLElement): boolean => { + const rect = element.getBoundingClientRect() + return rect.width > 0 && rect.height > 0 && rect.top < window.innerHeight && rect.bottom > 0 + } - // 图表初始化核心逻辑 - const performChartInit = (options: EChartsOption) => { - if (!chart && chartRef.value && !isDestroyed) { - chart = echarts.init(chartRef.value) - // 图表创建后立即设置监听器 - setupMenuWatchers() - setupThemeWatcher() - } - if (chart && !isDestroyed) { - chart.setOption(options) - pendingOptions = null - } - } + // 图表初始化核心逻辑 + const performChartInit = (options: EChartsOption) => { + if (!chart && chartRef.value && !isDestroyed) { + chart = echarts.init(chartRef.value) + // 图表创建后立即设置监听器 + setupMenuWatchers() + setupThemeWatcher() + } + if (chart && !isDestroyed) { + chart.setOption(options) + pendingOptions = null + } + } - // 空状态管理器 - const emptyStateManager = { - create: () => { - if (!chartRef.value || emptyStateDiv) return + // 空状态管理器 + const emptyStateManager = { + create: () => { + if (!chartRef.value || emptyStateDiv) return - emptyStateDiv = document.createElement('div') - emptyStateDiv.style.cssText = ` + emptyStateDiv = document.createElement('div') + emptyStateDiv.style.cssText = ` position: absolute; top: 0; left: 0; @@ -468,278 +468,278 @@ export function useChart(options: UseChartOptions = {}) { background: transparent; z-index: 10; ` - emptyStateDiv.innerHTML = `暂无数据` + emptyStateDiv.innerHTML = `暂无数据` - // 确保父容器有相对定位 - if ( - chartRef.value.style.position !== 'relative' && - chartRef.value.style.position !== 'absolute' - ) { - chartRef.value.style.position = 'relative' - } + // 确保父容器有相对定位 + if ( + chartRef.value.style.position !== 'relative' && + chartRef.value.style.position !== 'absolute' + ) { + chartRef.value.style.position = 'relative' + } - chartRef.value.appendChild(emptyStateDiv) - }, + chartRef.value.appendChild(emptyStateDiv) + }, - remove: () => { - if (emptyStateDiv && chartRef.value) { - chartRef.value.removeChild(emptyStateDiv) - emptyStateDiv = null - } - }, + remove: () => { + if (emptyStateDiv && chartRef.value) { + chartRef.value.removeChild(emptyStateDiv) + emptyStateDiv = null + } + }, - updateStyle: () => { - if (emptyStateDiv) { - emptyStateDiv.style.color = isDark.value ? '#666' : '#999' - } - } - } + updateStyle: () => { + if (emptyStateDiv) { + emptyStateDiv.style.color = isDark.value ? '#666' : '#999' + } + } + } - // 初始化图表 - const initChart = (options: EChartsOption = {}, isEmpty: boolean = false) => { - if (!chartRef.value || isDestroyed) return + // 初始化图表 + const initChart = (options: EChartsOption = {}, isEmpty: boolean = false) => { + if (!chartRef.value || isDestroyed) return - const mergedOptions = { ...initOptions, ...options } + const mergedOptions = { ...initOptions, ...options } - try { - if (isEmpty) { - // 处理空数据情况 - 显示自定义空状态div - if (chart) { - chart.clear() - } - emptyStateManager.create() - return - } else { - // 有数据时移除空状态div - emptyStateManager.remove() - } + try { + if (isEmpty) { + // 处理空数据情况 - 显示自定义空状态div + if (chart) { + chart.clear() + } + emptyStateManager.create() + return + } else { + // 有数据时移除空状态div + emptyStateManager.remove() + } - if (isContainerVisible(chartRef.value)) { - // 容器可见,正常初始化 - if (initDelay > 0) { - setTimeout(() => performChartInit(mergedOptions), initDelay) - } else { - performChartInit(mergedOptions) - } - } else { - // 容器不可见,保存选项并设置监听器 - pendingOptions = mergedOptions - createIntersectionObserver() - } - } catch (error) { - console.error('图表初始化失败:', error) - } - } + if (isContainerVisible(chartRef.value)) { + // 容器可见,正常初始化 + if (initDelay > 0) { + setTimeout(() => performChartInit(mergedOptions), initDelay) + } else { + performChartInit(mergedOptions) + } + } else { + // 容器不可见,保存选项并设置监听器 + pendingOptions = mergedOptions + createIntersectionObserver() + } + } catch (error) { + console.error('图表初始化失败:', error) + } + } - // 更新图表 - const updateChart = (options: EChartsOption) => { - if (isDestroyed) return + // 更新图表 + const updateChart = (options: EChartsOption) => { + if (isDestroyed) return - try { - if (!chart) { - // 如果图表不存在,先初始化 - initChart(options) - return - } - chart.setOption(options) - } catch (error) { - console.error('图表更新失败:', error) - } - } + try { + if (!chart) { + // 如果图表不存在,先初始化 + initChart(options) + return + } + chart.setOption(options) + } catch (error) { + console.error('图表更新失败:', error) + } + } - // 处理窗口大小变化 - const handleResize = () => { - if (chart && !isDestroyed) { - try { - chart.resize() - } catch (error) { - console.error('图表resize失败:', error) - } - } - } + // 处理窗口大小变化 + const handleResize = () => { + if (chart && !isDestroyed) { + try { + chart.resize() + } catch (error) { + console.error('图表resize失败:', error) + } + } + } - // 销毁图表 - const destroyChart = () => { - isDestroyed = true + // 销毁图表 + const destroyChart = () => { + isDestroyed = true - if (chart) { - try { - chart.dispose() - } catch (error) { - console.error('图表销毁失败:', error) - } finally { - chart = null - } - } + if (chart) { + try { + chart.dispose() + } catch (error) { + console.error('图表销毁失败:', error) + } finally { + chart = null + } + } - // 清理所有监听器和资源 - cleanupMenuWatchers() - cleanupThemeWatcher() - emptyStateManager.remove() - cleanupIntersectionObserver() - clearTimers() - clearStyleCache() - pendingOptions = null - } + // 清理所有监听器和资源 + cleanupMenuWatchers() + cleanupThemeWatcher() + emptyStateManager.remove() + cleanupIntersectionObserver() + clearTimers() + clearStyleCache() + pendingOptions = null + } - // 获取图表实例 - const getChartInstance = () => chart + // 获取图表实例 + const getChartInstance = () => chart - // 获取图表是否已初始化 - const isChartInitialized = () => chart !== null + // 获取图表是否已初始化 + const isChartInitialized = () => chart !== null - onMounted(() => { - window.addEventListener('resize', debouncedResize) - }) + onMounted(() => { + window.addEventListener('resize', debouncedResize) + }) - onBeforeUnmount(() => { - window.removeEventListener('resize', debouncedResize) - }) + onBeforeUnmount(() => { + window.removeEventListener('resize', debouncedResize) + }) - onUnmounted(() => { - destroyChart() - }) + onUnmounted(() => { + destroyChart() + }) - return { - isDark, - chartRef, - initChart, - updateChart, - handleResize, - destroyChart, - getChartInstance, - isChartInitialized, - emptyStateManager, - getAxisLineStyle, - getSplitLineStyle, - getAxisLabelStyle, - getAxisTickStyle, - getAnimationConfig, - getTooltipStyle, - getLegendStyle, - useChartOps, - getGridWithLegend - } + return { + isDark, + chartRef, + initChart, + updateChart, + handleResize, + destroyChart, + getChartInstance, + isChartInitialized, + emptyStateManager, + getAxisLineStyle, + getSplitLineStyle, + getAxisLabelStyle, + getAxisTickStyle, + getAnimationConfig, + getTooltipStyle, + getLegendStyle, + useChartOps, + getGridWithLegend + } } // 高级图表组件抽象 interface UseChartComponentOptions { - /** Props响应式对象 */ - props: T - /** 图表配置生成函数 */ - generateOptions: () => EChartsOption - /** 空数据检查函数 */ - checkEmpty?: () => boolean - /** 自定义监听的响应式数据 */ - watchSources?: (() => any)[] - /** 自定义可视事件处理 */ - onVisible?: () => void - /** useChart选项 */ - chartOptions?: UseChartOptions + /** Props响应式对象 */ + props: T + /** 图表配置生成函数 */ + generateOptions: () => EChartsOption + /** 空数据检查函数 */ + checkEmpty?: () => boolean + /** 自定义监听的响应式数据 */ + watchSources?: (() => any)[] + /** 自定义可视事件处理 */ + onVisible?: () => void + /** useChart选项 */ + chartOptions?: UseChartOptions } export function useChartComponent(options: UseChartComponentOptions) { - const { - props, - generateOptions, - checkEmpty, - watchSources = [], - onVisible, - chartOptions = {} - } = options + const { + props, + generateOptions, + checkEmpty, + watchSources = [], + onVisible, + chartOptions = {} + } = options - const chart = useChart(chartOptions) - const { chartRef, initChart, isDark, emptyStateManager } = chart + const chart = useChart(chartOptions) + const { chartRef, initChart, isDark, emptyStateManager } = chart - // 检查是否为空数据 - const isEmpty = computed(() => { - if (props.isEmpty) return true - if (checkEmpty) return checkEmpty() - return false - }) + // 检查是否为空数据 + const isEmpty = computed(() => { + if (props.isEmpty) return true + if (checkEmpty) return checkEmpty() + return false + }) - // 更新图表 - const updateChart = () => { - nextTick(() => { - if (isEmpty.value) { - // 处理空数据情况 - 显示自定义空状态div - if (chart.getChartInstance()) { - chart.getChartInstance()?.clear() - } - emptyStateManager.create() - } else { - // 有数据时移除空状态div并初始化图表 - emptyStateManager.remove() - initChart(generateOptions()) - } - }) - } + // 更新图表 + const updateChart = () => { + nextTick(() => { + if (isEmpty.value) { + // 处理空数据情况 - 显示自定义空状态div + if (chart.getChartInstance()) { + chart.getChartInstance()?.clear() + } + emptyStateManager.create() + } else { + // 有数据时移除空状态div并初始化图表 + emptyStateManager.remove() + initChart(generateOptions()) + } + }) + } - // 处理图表进入可视区域时的逻辑 - const handleChartVisible = () => { - if (onVisible) { - onVisible() - } else { - updateChart() - } - } + // 处理图表进入可视区域时的逻辑 + const handleChartVisible = () => { + if (onVisible) { + onVisible() + } else { + updateChart() + } + } - // 存储监听器停止函数 - const stopHandles: (() => void)[] = [] + // 存储监听器停止函数 + const stopHandles: (() => void)[] = [] - // 设置数据监听 - const setupWatchers = () => { - // 监听自定义数据源 - if (watchSources.length > 0) { - const stopHandle = watch(watchSources, updateChart, { deep: true }) - stopHandles.push(stopHandle) - } + // 设置数据监听 + const setupWatchers = () => { + // 监听自定义数据源 + if (watchSources.length > 0) { + const stopHandle = watch(watchSources, updateChart, { deep: true }) + stopHandles.push(stopHandle) + } - // 监听主题变化 - const themeStopHandle = watch(isDark, () => { - emptyStateManager.updateStyle() - updateChart() - }) - stopHandles.push(themeStopHandle) - } + // 监听主题变化 + const themeStopHandle = watch(isDark, () => { + emptyStateManager.updateStyle() + updateChart() + }) + stopHandles.push(themeStopHandle) + } - // 清理所有监听器 - const cleanupWatchers = () => { - stopHandles.forEach((stop) => stop()) - stopHandles.length = 0 - } + // 清理所有监听器 + const cleanupWatchers = () => { + stopHandles.forEach((stop) => stop()) + stopHandles.length = 0 + } - // 设置生命周期 - const setupLifecycle = () => { - onMounted(() => { - updateChart() + // 设置生命周期 + const setupLifecycle = () => { + onMounted(() => { + updateChart() - // 监听图表可见事件 - if (chartRef.value) { - chartRef.value.addEventListener('chartVisible', handleChartVisible) - } - }) + // 监听图表可见事件 + if (chartRef.value) { + chartRef.value.addEventListener('chartVisible', handleChartVisible) + } + }) - onBeforeUnmount(() => { - // 清理事件监听器 - if (chartRef.value) { - chartRef.value.removeEventListener('chartVisible', handleChartVisible) - } - // 清理所有监听器 - cleanupWatchers() - // 清理空状态div - emptyStateManager.remove() - }) - } + onBeforeUnmount(() => { + // 清理事件监听器 + if (chartRef.value) { + chartRef.value.removeEventListener('chartVisible', handleChartVisible) + } + // 清理所有监听器 + cleanupWatchers() + // 清理空状态div + emptyStateManager.remove() + }) + } - // 初始化 - setupWatchers() - setupLifecycle() + // 初始化 + setupWatchers() + setupLifecycle() - return { - ...chart, - isEmpty, - updateChart, - handleChartVisible - } + return { + ...chart, + isEmpty, + updateChart, + handleChartVisible + } } diff --git a/src/hooks/core/useCommon.ts b/src/hooks/core/useCommon.ts index c936854..f06e42f 100644 --- a/src/hooks/core/useCommon.ts +++ b/src/hooks/core/useCommon.ts @@ -20,68 +20,68 @@ import { useMenuStore } from '@/store/modules/menu' import { useSettingStore } from '@/store/modules/setting' export function useCommon() { - const menuStore = useMenuStore() - const settingStore = useSettingStore() + const menuStore = useMenuStore() + const settingStore = useSettingStore() - /** - * 首页路径 - * 从菜单 store 中获取配置的首页路径 - */ - const homePath = computed(() => menuStore.getHomePath()) + /** + * 首页路径 + * 从菜单 store 中获取配置的首页路径 + */ + const homePath = computed(() => menuStore.getHomePath()) - /** - * 刷新当前页面 - * 通过切换 setting store 中的 refresh 状态触发页面重新渲染 - */ - const refresh = () => { - settingStore.reload() - } + /** + * 刷新当前页面 + * 通过切换 setting store 中的 refresh 状态触发页面重新渲染 + */ + const refresh = () => { + settingStore.reload() + } - /** - * 滚动到页面顶部 - * 查找主内容区域并将其滚动位置重置为顶部 - */ - const scrollToTop = () => { - const scrollContainer = document.getElementById('app-main') - if (scrollContainer) { - scrollContainer.scrollTop = 0 - } - } + /** + * 滚动到页面顶部 + * 查找主内容区域并将其滚动位置重置为顶部 + */ + const scrollToTop = () => { + const scrollContainer = document.getElementById('app-main') + if (scrollContainer) { + scrollContainer.scrollTop = 0 + } + } - /** - * 平滑滚动到页面顶部 - * 使用 smooth 行为实现平滑滚动效果 - */ - const smoothScrollToTop = () => { - const scrollContainer = document.getElementById('app-main') - if (scrollContainer) { - scrollContainer.scrollTo({ - top: 0, - behavior: 'smooth' - }) - } - } + /** + * 平滑滚动到页面顶部 + * 使用 smooth 行为实现平滑滚动效果 + */ + const smoothScrollToTop = () => { + const scrollContainer = document.getElementById('app-main') + if (scrollContainer) { + scrollContainer.scrollTo({ + top: 0, + behavior: 'smooth' + }) + } + } - /** - * 滚动到指定位置 - * @param top 目标滚动位置(像素) - * @param smooth 是否使用平滑滚动 - */ - const scrollTo = (top: number, smooth: boolean = false) => { - const scrollContainer = document.getElementById('app-main') - if (scrollContainer) { - scrollContainer.scrollTo({ - top, - behavior: smooth ? 'smooth' : 'auto' - }) - } - } + /** + * 滚动到指定位置 + * @param top 目标滚动位置(像素) + * @param smooth 是否使用平滑滚动 + */ + const scrollTo = (top: number, smooth: boolean = false) => { + const scrollContainer = document.getElementById('app-main') + if (scrollContainer) { + scrollContainer.scrollTo({ + top, + behavior: smooth ? 'smooth' : 'auto' + }) + } + } - return { - homePath, - refresh, - scrollTo, - scrollToTop, - smoothScrollToTop - } + return { + homePath, + refresh, + scrollTo, + scrollToTop, + smoothScrollToTop + } } diff --git a/src/hooks/core/useFastEnter.ts b/src/hooks/core/useFastEnter.ts index 555eb65..ef168a9 100644 --- a/src/hooks/core/useFastEnter.ts +++ b/src/hooks/core/useFastEnter.ts @@ -20,36 +20,36 @@ import appConfig from '@/config' import type { FastEnterApplication, FastEnterQuickLink } from '@/types/config' export function useFastEnter() { - // 获取快速入口配置 - const fastEnterConfig = computed(() => appConfig.fastEnter) + // 获取快速入口配置 + const fastEnterConfig = computed(() => appConfig.fastEnter) - // 获取启用的应用列表(按排序权重排序) - const enabledApplications = computed(() => { - if (!fastEnterConfig.value?.applications) return [] + // 获取启用的应用列表(按排序权重排序) + const enabledApplications = computed(() => { + if (!fastEnterConfig.value?.applications) return [] - return fastEnterConfig.value.applications - .filter((app) => app.enabled !== false) - .sort((a, b) => (a.order || 0) - (b.order || 0)) - }) + return fastEnterConfig.value.applications + .filter((app) => app.enabled !== false) + .sort((a, b) => (a.order || 0) - (b.order || 0)) + }) - // 获取启用的快速链接(按排序权重排序) - const enabledQuickLinks = computed(() => { - if (!fastEnterConfig.value?.quickLinks) return [] + // 获取启用的快速链接(按排序权重排序) + const enabledQuickLinks = computed(() => { + if (!fastEnterConfig.value?.quickLinks) return [] - return fastEnterConfig.value.quickLinks - .filter((link) => link.enabled !== false) - .sort((a, b) => (a.order || 0) - (b.order || 0)) - }) + return fastEnterConfig.value.quickLinks + .filter((link) => link.enabled !== false) + .sort((a, b) => (a.order || 0) - (b.order || 0)) + }) - // 获取最小显示宽度 - const minWidth = computed(() => { - return fastEnterConfig.value?.minWidth || 1200 - }) + // 获取最小显示宽度 + const minWidth = computed(() => { + return fastEnterConfig.value?.minWidth || 1200 + }) - return { - fastEnterConfig, - enabledApplications, - enabledQuickLinks, - minWidth - } + return { + fastEnterConfig, + enabledApplications, + enabledQuickLinks, + minWidth + } } diff --git a/src/hooks/core/useHeaderBar.ts b/src/hooks/core/useHeaderBar.ts index be10712..d8c265d 100644 --- a/src/hooks/core/useHeaderBar.ts +++ b/src/hooks/core/useHeaderBar.ts @@ -26,176 +26,176 @@ import { HeaderBarFeatureConfig } from '@/types' * @returns 顶部栏功能相关的状态和方法 */ export function useHeaderBar() { - const settingStore = useSettingStore() + const settingStore = useSettingStore() - // 获取顶部栏配置 - const headerBarConfigRef = computed(() => headerBarConfig) + // 获取顶部栏配置 + const headerBarConfigRef = computed(() => headerBarConfig) - // 从store中获取相关状态 - const { showMenuButton, showFastEnter, showRefreshButton, showCrumbs, showLanguage } = - storeToRefs(settingStore) + // 从store中获取相关状态 + const { showMenuButton, showFastEnter, showRefreshButton, showCrumbs, showLanguage } = + storeToRefs(settingStore) - /** - * 检查特定功能是否启用 - * @param feature 功能名称 - * @returns 是否启用 - */ - const isFeatureEnabled = (feature: keyof HeaderBarFeatureConfig): boolean => { - return headerBarConfigRef.value[feature]?.enabled ?? false - } + /** + * 检查特定功能是否启用 + * @param feature 功能名称 + * @returns 是否启用 + */ + const isFeatureEnabled = (feature: keyof HeaderBarFeatureConfig): boolean => { + return headerBarConfigRef.value[feature]?.enabled ?? false + } - /** - * 获取功能配置信息 - * @param feature 功能名称 - * @returns 功能配置信息 - */ - const getFeatureConfig = (feature: keyof HeaderBarFeatureConfig) => { - return headerBarConfigRef.value[feature] - } + /** + * 获取功能配置信息 + * @param feature 功能名称 + * @returns 功能配置信息 + */ + const getFeatureConfig = (feature: keyof HeaderBarFeatureConfig) => { + return headerBarConfigRef.value[feature] + } - // 检查菜单按钮是否显示 - const shouldShowMenuButton = computed(() => { - return isFeatureEnabled('menuButton') && showMenuButton.value - }) + // 检查菜单按钮是否显示 + const shouldShowMenuButton = computed(() => { + return isFeatureEnabled('menuButton') && showMenuButton.value + }) - // 检查刷新按钮是否显示 - const shouldShowRefreshButton = computed(() => { - return isFeatureEnabled('refreshButton') && showRefreshButton.value - }) + // 检查刷新按钮是否显示 + const shouldShowRefreshButton = computed(() => { + return isFeatureEnabled('refreshButton') && showRefreshButton.value + }) - // 检查快速入口是否显示 - const shouldShowFastEnter = computed(() => { - return isFeatureEnabled('fastEnter') && showFastEnter.value - }) + // 检查快速入口是否显示 + const shouldShowFastEnter = computed(() => { + return isFeatureEnabled('fastEnter') && showFastEnter.value + }) - // 检查面包屑是否显示 - const shouldShowBreadcrumb = computed(() => { - return isFeatureEnabled('breadcrumb') && showCrumbs.value - }) + // 检查面包屑是否显示 + const shouldShowBreadcrumb = computed(() => { + return isFeatureEnabled('breadcrumb') && showCrumbs.value + }) - // 检查全局搜索是否显示 - const shouldShowGlobalSearch = computed(() => { - return isFeatureEnabled('globalSearch') - }) + // 检查全局搜索是否显示 + const shouldShowGlobalSearch = computed(() => { + return isFeatureEnabled('globalSearch') + }) - // 检查全屏按钮是否显示 - const shouldShowFullscreen = computed(() => { - return isFeatureEnabled('fullscreen') - }) + // 检查全屏按钮是否显示 + const shouldShowFullscreen = computed(() => { + return isFeatureEnabled('fullscreen') + }) - // 检查通知中心是否显示 - const shouldShowNotification = computed(() => { - return isFeatureEnabled('notification') - }) + // 检查通知中心是否显示 + const shouldShowNotification = computed(() => { + return isFeatureEnabled('notification') + }) - // 检查聊天功能是否显示 - const shouldShowChat = computed(() => { - return isFeatureEnabled('chat') - }) + // 检查聊天功能是否显示 + const shouldShowChat = computed(() => { + return isFeatureEnabled('chat') + }) - // 检查语言切换是否显示 - const shouldShowLanguage = computed(() => { - return isFeatureEnabled('language') && showLanguage.value - }) + // 检查语言切换是否显示 + const shouldShowLanguage = computed(() => { + return isFeatureEnabled('language') && showLanguage.value + }) - // 检查设置面板是否显示 - const shouldShowSettings = computed(() => { - return isFeatureEnabled('settings') - }) + // 检查设置面板是否显示 + const shouldShowSettings = computed(() => { + return isFeatureEnabled('settings') + }) - // 检查主题切换是否显示 - const shouldShowThemeToggle = computed(() => { - return isFeatureEnabled('themeToggle') - }) + // 检查主题切换是否显示 + const shouldShowThemeToggle = computed(() => { + return isFeatureEnabled('themeToggle') + }) - // 获取快速入口的最小宽度 - const fastEnterMinWidth = computed(() => { - const config = getFeatureConfig('fastEnter') - return (config as any)?.minWidth || 1200 - }) + // 获取快速入口的最小宽度 + const fastEnterMinWidth = computed(() => { + const config = getFeatureConfig('fastEnter') + return (config as any)?.minWidth || 1200 + }) - /** - * 检查功能是否启用(别名) - * @param feature 功能名称 - * @returns 是否启用 - */ - const isFeatureActive = (feature: keyof HeaderBarFeatureConfig): boolean => { - return isFeatureEnabled(feature) - } + /** + * 检查功能是否启用(别名) + * @param feature 功能名称 + * @returns 是否启用 + */ + const isFeatureActive = (feature: keyof HeaderBarFeatureConfig): boolean => { + return isFeatureEnabled(feature) + } - /** - * 获取功能配置(别名) - * @param feature 功能名称 - * @returns 功能配置 - */ - const getFeatureInfo = (feature: keyof HeaderBarFeatureConfig) => { - return getFeatureConfig(feature) - } + /** + * 获取功能配置(别名) + * @param feature 功能名称 + * @returns 功能配置 + */ + const getFeatureInfo = (feature: keyof HeaderBarFeatureConfig) => { + return getFeatureConfig(feature) + } - /** - * 获取所有启用的功能列表 - * @returns 启用的功能名称数组 - */ - const getEnabledFeatures = (): (keyof HeaderBarFeatureConfig)[] => { - return Object.keys(headerBarConfigRef.value).filter( - (key) => headerBarConfigRef.value[key as keyof HeaderBarFeatureConfig]?.enabled - ) as (keyof HeaderBarFeatureConfig)[] - } + /** + * 获取所有启用的功能列表 + * @returns 启用的功能名称数组 + */ + const getEnabledFeatures = (): (keyof HeaderBarFeatureConfig)[] => { + return Object.keys(headerBarConfigRef.value).filter( + (key) => headerBarConfigRef.value[key as keyof HeaderBarFeatureConfig]?.enabled + ) as (keyof HeaderBarFeatureConfig)[] + } - /** - * 获取所有禁用的功能列表 - * @returns 禁用的功能名称数组 - */ - const getDisabledFeatures = (): (keyof HeaderBarFeatureConfig)[] => { - return Object.keys(headerBarConfigRef.value).filter( - (key) => !headerBarConfigRef.value[key as keyof HeaderBarFeatureConfig]?.enabled - ) as (keyof HeaderBarFeatureConfig)[] - } + /** + * 获取所有禁用的功能列表 + * @returns 禁用的功能名称数组 + */ + const getDisabledFeatures = (): (keyof HeaderBarFeatureConfig)[] => { + return Object.keys(headerBarConfigRef.value).filter( + (key) => !headerBarConfigRef.value[key as keyof HeaderBarFeatureConfig]?.enabled + ) as (keyof HeaderBarFeatureConfig)[] + } - /** - * 获取所有启用的功能(别名) - * @returns 启用的功能列表 - */ - const getActiveFeatures = () => { - return getEnabledFeatures() - } + /** + * 获取所有启用的功能(别名) + * @returns 启用的功能列表 + */ + const getActiveFeatures = () => { + return getEnabledFeatures() + } - /** - * 获取所有禁用的功能(别名) - * @returns 禁用的功能列表 - */ - const getInactiveFeatures = () => { - return getDisabledFeatures() - } + /** + * 获取所有禁用的功能(别名) + * @returns 禁用的功能列表 + */ + const getInactiveFeatures = () => { + return getDisabledFeatures() + } - return { - // 配置 - headerBarConfig: headerBarConfigRef, + return { + // 配置 + headerBarConfig: headerBarConfigRef, - // 显示状态计算属性 - shouldShowMenuButton, // 是否显示菜单按钮 - shouldShowRefreshButton, // 是否显示刷新按钮 - shouldShowFastEnter, // 是否显示快速入口 - shouldShowBreadcrumb, // 是否显示面包屑 - shouldShowGlobalSearch, // 是否显示全局搜索 - shouldShowFullscreen, // 是否显示全屏按钮 - shouldShowNotification, // 是否显示通知中心 - shouldShowChat, // 是否显示聊天功能 - shouldShowLanguage, // 是否显示语言切换 - shouldShowSettings, // 是否显示设置面板 - shouldShowThemeToggle, // 是否显示主题切换 + // 显示状态计算属性 + shouldShowMenuButton, // 是否显示菜单按钮 + shouldShowRefreshButton, // 是否显示刷新按钮 + shouldShowFastEnter, // 是否显示快速入口 + shouldShowBreadcrumb, // 是否显示面包屑 + shouldShowGlobalSearch, // 是否显示全局搜索 + shouldShowFullscreen, // 是否显示全屏按钮 + shouldShowNotification, // 是否显示通知中心 + shouldShowChat, // 是否显示聊天功能 + shouldShowLanguage, // 是否显示语言切换 + shouldShowSettings, // 是否显示设置面板 + shouldShowThemeToggle, // 是否显示主题切换 - // 配置相关 - fastEnterMinWidth, // 快速入口最小宽度 + // 配置相关 + fastEnterMinWidth, // 快速入口最小宽度 - // 方法 - isFeatureEnabled, // 检查功能是否启用 - isFeatureActive, // 检查功能是否启用(别名) - getFeatureConfig, // 获取功能配置 - getFeatureInfo, // 获取功能配置(别名) - getEnabledFeatures, // 获取所有启用的功能 - getDisabledFeatures, // 获取所有禁用的功能 - getActiveFeatures, // 获取所有启用的功能(别名) - getInactiveFeatures // 获取所有禁用的功能(别名) - } + // 方法 + isFeatureEnabled, // 检查功能是否启用 + isFeatureActive, // 检查功能是否启用(别名) + getFeatureConfig, // 获取功能配置 + getFeatureInfo, // 获取功能配置(别名) + getEnabledFeatures, // 获取所有启用的功能 + getDisabledFeatures, // 获取所有禁用的功能 + getActiveFeatures, // 获取所有启用的功能(别名) + getInactiveFeatures // 获取所有禁用的功能(别名) + } } diff --git a/src/hooks/core/useLayoutHeight.ts b/src/hooks/core/useLayoutHeight.ts index 4b1171a..24e68f3 100644 --- a/src/hooks/core/useLayoutHeight.ts +++ b/src/hooks/core/useLayoutHeight.ts @@ -23,55 +23,55 @@ import { useElementSize } from '@vueuse/core' * 页面容器高度配置 */ interface LayoutHeightOptions { - /** 额外的间距(默认 15px) */ - extraSpacing?: number - /** 是否自动更新 CSS 变量(默认 true) */ - updateCssVar?: boolean - /** CSS 变量名称(默认 '--art-full-height') */ - cssVarName?: string + /** 额外的间距(默认 15px) */ + extraSpacing?: number + /** 是否自动更新 CSS 变量(默认 true) */ + updateCssVar?: boolean + /** CSS 变量名称(默认 '--art-full-height') */ + cssVarName?: string } export function useLayoutHeight(options: LayoutHeightOptions = {}) { - const { extraSpacing = 15, updateCssVar = true, cssVarName = '--art-full-height' } = options + const { extraSpacing = 15, updateCssVar = true, cssVarName = '--art-full-height' } = options - // 元素引用 - const headerRef = ref() - const contentHeaderRef = ref() + // 元素引用 + const headerRef = ref() + const contentHeaderRef = ref() - // 使用 VueUse 自动监听元素尺寸变化 - const { height: headerHeight } = useElementSize(headerRef) - const { height: contentHeaderHeight } = useElementSize(contentHeaderRef) + // 使用 VueUse 自动监听元素尺寸变化 + const { height: headerHeight } = useElementSize(headerRef) + const { height: contentHeaderHeight } = useElementSize(contentHeaderRef) - // 计算容器最小高度(响应式) - const containerMinHeight = computed(() => { - const totalHeight = headerHeight.value + contentHeaderHeight.value + extraSpacing - return `calc(100vh - ${totalHeight}px)` - }) + // 计算容器最小高度(响应式) + const containerMinHeight = computed(() => { + const totalHeight = headerHeight.value + contentHeaderHeight.value + extraSpacing + return `calc(100vh - ${totalHeight}px)` + }) - if (updateCssVar) { - watch( - containerMinHeight, - (newHeight) => { - requestAnimationFrame(() => { - document.documentElement.style.setProperty(cssVarName, newHeight) - }) - }, - { immediate: true } - ) - } + if (updateCssVar) { + watch( + containerMinHeight, + (newHeight) => { + requestAnimationFrame(() => { + document.documentElement.style.setProperty(cssVarName, newHeight) + }) + }, + { immediate: true } + ) + } - return { - /** 容器最小高度(响应式) */ - containerMinHeight, - /** 头部元素引用 */ - headerRef, - /** 内容头部元素引用 */ - contentHeaderRef, - /** 头部高度(响应式) */ - headerHeight, - /** 内容头部高度(响应式) */ - contentHeaderHeight - } + return { + /** 容器最小高度(响应式) */ + containerMinHeight, + /** 头部元素引用 */ + headerRef, + /** 内容头部元素引用 */ + contentHeaderRef, + /** 头部高度(响应式) */ + headerHeight, + /** 内容头部高度(响应式) */ + contentHeaderHeight + } } /** @@ -84,65 +84,65 @@ export function useLayoutHeight(options: LayoutHeightOptions = {}) { * ``` */ export function useAutoLayoutHeight( - headerIds: string[] = ['app-header', 'app-content-header'], - options: LayoutHeightOptions = {} + headerIds: string[] = ['app-header', 'app-content-header'], + options: LayoutHeightOptions = {} ) { - const { extraSpacing = 15, updateCssVar = true, cssVarName = '--art-full-height' } = options + const { extraSpacing = 15, updateCssVar = true, cssVarName = '--art-full-height' } = options - // 创建元素引用 - const headerRef = ref() - const contentHeaderRef = ref() + // 创建元素引用 + const headerRef = ref() + const contentHeaderRef = ref() - // 使用 VueUse 自动监听元素尺寸变化 - const { height: headerHeight } = useElementSize(headerRef) - const { height: contentHeaderHeight } = useElementSize(contentHeaderRef) + // 使用 VueUse 自动监听元素尺寸变化 + const { height: headerHeight } = useElementSize(headerRef) + const { height: contentHeaderHeight } = useElementSize(contentHeaderRef) - // 计算容器最小高度(响应式) - const containerMinHeight = computed(() => { - const totalHeight = headerHeight.value + contentHeaderHeight.value + extraSpacing - return `calc(100vh - ${totalHeight}px)` - }) + // 计算容器最小高度(响应式) + const containerMinHeight = computed(() => { + const totalHeight = headerHeight.value + contentHeaderHeight.value + extraSpacing + return `calc(100vh - ${totalHeight}px)` + }) - if (updateCssVar) { - watch( - containerMinHeight, - (newHeight) => { - requestAnimationFrame(() => { - document.documentElement.style.setProperty(cssVarName, newHeight) - }) - }, - { immediate: true } - ) - } + if (updateCssVar) { + watch( + containerMinHeight, + (newHeight) => { + requestAnimationFrame(() => { + document.documentElement.style.setProperty(cssVarName, newHeight) + }) + }, + { immediate: true } + ) + } - // 在 DOM 挂载后查找元素 - onMounted(() => { - if (typeof document !== 'undefined') { - // 使用 nextTick 确保 DOM 完全渲染 - requestAnimationFrame(() => { - const header = document.getElementById(headerIds[0]) - const contentHeader = document.getElementById(headerIds[1]) + // 在 DOM 挂载后查找元素 + onMounted(() => { + if (typeof document !== 'undefined') { + // 使用 nextTick 确保 DOM 完全渲染 + requestAnimationFrame(() => { + const header = document.getElementById(headerIds[0]) + const contentHeader = document.getElementById(headerIds[1]) - if (header) { - headerRef.value = header - } - if (contentHeader) { - contentHeaderRef.value = contentHeader - } - }) - } - }) + if (header) { + headerRef.value = header + } + if (contentHeader) { + contentHeaderRef.value = contentHeader + } + }) + } + }) - return { - /** 容器最小高度(响应式) */ - containerMinHeight, - /** 头部元素引用 */ - headerRef, - /** 内容头部元素引用 */ - contentHeaderRef, - /** 头部高度(响应式) */ - headerHeight, - /** 内容头部高度(响应式) */ - contentHeaderHeight - } + return { + /** 容器最小高度(响应式) */ + containerMinHeight, + /** 头部元素引用 */ + headerRef, + /** 内容头部元素引用 */ + contentHeaderRef, + /** 头部高度(响应式) */ + headerHeight, + /** 内容头部高度(响应式) */ + contentHeaderHeight + } } diff --git a/src/hooks/core/useTable.ts b/src/hooks/core/useTable.ts index afe9ebe..698c632 100644 --- a/src/hooks/core/useTable.ts +++ b/src/hooks/core/useTable.ts @@ -22,17 +22,17 @@ import { useWindowSize } from '@vueuse/core' import { useTableColumns } from './useTableColumns' import type { ColumnOption } from '@/types/component' import { - TableCache, - CacheInvalidationStrategy, - type ApiResponse + TableCache, + CacheInvalidationStrategy, + type ApiResponse } from '../../utils/table/tableCache' import { - type TableError, - defaultResponseAdapter, - extractTableData, - updatePaginationFromResponse, - createSmartDebounce, - createErrorHandler + type TableError, + defaultResponseAdapter, + extractTableData, + updatePaginationFromResponse, + createSmartDebounce, + createErrorHandler } from '../../utils/table/tableUtils' import { tableConfig } from '../../utils/table/tableConfig' @@ -43,79 +43,79 @@ type InferRecordType = T extends Api.Common.PaginatedResponse ? U : // 优化的配置接口 - 支持自动类型推导 export interface UseTableConfig< - TApiFn extends (params: any) => Promise = (params: any) => Promise, - TRecord = InferRecordType>, - TParams = InferApiParams, - TResponse = InferApiResponse + TApiFn extends (params: any) => Promise = (params: any) => Promise, + TRecord = InferRecordType>, + TParams = InferApiParams, + TResponse = InferApiResponse > { - // 核心配置 - core: { - /** API 请求函数 */ - apiFn: TApiFn - /** 默认请求参数 */ - apiParams?: Partial - /** 排除 apiParams 中的属性 */ - excludeParams?: string[] - /** 是否立即加载数据 */ - immediate?: boolean - /** 列配置工厂函数 */ - columnsFactory?: () => ColumnOption[] - /** 自定义分页字段映射 */ - paginationKey?: { - /** 当前页码字段名,默认为 'current' */ - current?: string - /** 每页条数字段名,默认为 'size' */ - size?: string - } - } + // 核心配置 + core: { + /** API 请求函数 */ + apiFn: TApiFn + /** 默认请求参数 */ + apiParams?: Partial + /** 排除 apiParams 中的属性 */ + excludeParams?: string[] + /** 是否立即加载数据 */ + immediate?: boolean + /** 列配置工厂函数 */ + columnsFactory?: () => ColumnOption[] + /** 自定义分页字段映射 */ + paginationKey?: { + /** 当前页码字段名,默认为 'current' */ + current?: string + /** 每页条数字段名,默认为 'size' */ + size?: string + } + } - // 数据处理 - transform?: { - /** 数据转换函数 */ - dataTransformer?: (data: TRecord[]) => TRecord[] - /** 响应数据适配器 */ - responseAdapter?: (response: TResponse) => ApiResponse - } + // 数据处理 + transform?: { + /** 数据转换函数 */ + dataTransformer?: (data: TRecord[]) => TRecord[] + /** 响应数据适配器 */ + responseAdapter?: (response: TResponse) => ApiResponse + } - // 性能优化 - performance?: { - /** 是否启用缓存 */ - enableCache?: boolean - /** 缓存时间(毫秒) */ - cacheTime?: number - /** 防抖延迟时间(毫秒) */ - debounceTime?: number - /** 最大缓存条数限制 */ - maxCacheSize?: number - } + // 性能优化 + performance?: { + /** 是否启用缓存 */ + enableCache?: boolean + /** 缓存时间(毫秒) */ + cacheTime?: number + /** 防抖延迟时间(毫秒) */ + debounceTime?: number + /** 最大缓存条数限制 */ + maxCacheSize?: number + } - // 生命周期钩子 - hooks?: { - /** 数据加载成功回调(仅网络请求成功时触发) */ - onSuccess?: (data: TRecord[], response: ApiResponse) => void - /** 错误处理回调 */ - onError?: (error: TableError) => void - /** 缓存命中回调(从缓存获取数据时触发) */ - onCacheHit?: (data: TRecord[], response: ApiResponse) => void - /** 加载状态变化回调 */ - onLoading?: (loading: boolean) => void - /** 重置表单回调函数 */ - resetFormCallback?: () => void - } + // 生命周期钩子 + hooks?: { + /** 数据加载成功回调(仅网络请求成功时触发) */ + onSuccess?: (data: TRecord[], response: ApiResponse) => void + /** 错误处理回调 */ + onError?: (error: TableError) => void + /** 缓存命中回调(从缓存获取数据时触发) */ + onCacheHit?: (data: TRecord[], response: ApiResponse) => void + /** 加载状态变化回调 */ + onLoading?: (loading: boolean) => void + /** 重置表单回调函数 */ + resetFormCallback?: () => void + } - // 调试配置 - debug?: { - /** 是否启用日志输出 */ - enableLog?: boolean - /** 日志级别 */ - logLevel?: 'info' | 'warn' | 'error' - } + // 调试配置 + debug?: { + /** 是否启用日志输出 */ + enableLog?: boolean + /** 日志级别 */ + logLevel?: 'info' | 'warn' | 'error' + } } export function useTable Promise>( - config: UseTableConfig + config: UseTableConfig ) { - return useTableImpl(config) + return useTableImpl(config) } /** @@ -130,604 +130,606 @@ export function useTable Promise>( * - 列配置管理 */ function useTableImpl Promise>( - config: UseTableConfig + config: UseTableConfig ) { - type TRecord = InferRecordType> - type TParams = InferApiParams - const { - core: { - apiFn, - apiParams = {} as Partial, - excludeParams = [], - immediate = true, - columnsFactory, - paginationKey - }, - transform: { dataTransformer, responseAdapter = defaultResponseAdapter } = {}, - performance: { - enableCache = false, - cacheTime = 5 * 60 * 1000, - debounceTime = 300, - maxCacheSize = 50 - } = {}, - hooks: { onSuccess, onError, onCacheHit, resetFormCallback } = {}, - debug: { enableLog = false } = {} - } = config - - // 分页字段名配置:优先使用传入的配置,否则使用全局配置 - const pageKey = paginationKey?.current || tableConfig.paginationKey.current - const sizeKey = paginationKey?.size || tableConfig.paginationKey.size - - // 响应式触发器,用于手动更新缓存统计信息 - const cacheUpdateTrigger = ref(0) - - // 日志工具函数 - const logger = { - log: (message: string, ...args: unknown[]) => { - if (enableLog) { - console.log(`[useTable] ${message}`, ...args) - } - }, - warn: (message: string, ...args: unknown[]) => { - if (enableLog) { - console.warn(`[useTable] ${message}`, ...args) - } - }, - error: (message: string, ...args: unknown[]) => { - if (enableLog) { - console.error(`[useTable] ${message}`, ...args) - } - } - } - - // 缓存实例 - const cache = enableCache ? new TableCache(cacheTime, maxCacheSize, enableLog) : null - - // 加载状态机 - type LoadingState = 'idle' | 'loading' | 'success' | 'error' - const loadingState = ref('idle') - const loading = computed(() => loadingState.value === 'loading') - - // 错误状态 - const error = ref(null) - - // 表格数据 - const data = ref([]) - - // 请求取消控制器 - let abortController: AbortController | null = null - - // 缓存清理定时器 - let cacheCleanupTimer: NodeJS.Timeout | null = null - - // 搜索参数 - const searchParams = reactive( - Object.assign( - { - [pageKey]: 1, - [sizeKey]: 10 - }, - apiParams || {} - ) as TParams - ) - - // 分页配置 - const pagination = reactive({ - current: ((searchParams as Record)[pageKey] as number) || 1, - size: ((searchParams as Record)[sizeKey] as number) || 10, - total: 0 - }) - - // 移动端分页 (响应式) - const { width } = useWindowSize() - const mobilePagination = computed(() => ({ - ...pagination, - small: width.value < 768 - })) - - // 列配置 - const columnConfig = columnsFactory ? useTableColumns(columnsFactory) : null - const columns = columnConfig?.columns - const columnChecks = columnConfig?.columnChecks - - // 是否有数据 - const hasData = computed(() => data.value.length > 0) - - // 缓存统计信息 - const cacheInfo = computed(() => { - // 依赖触发器,确保缓存变化时重新计算 - void cacheUpdateTrigger.value - if (!cache) return { total: 0, size: '0KB', hitRate: '0 avg hits' } - return cache.getStats() - }) - - // 错误处理函数 - const handleError = createErrorHandler(onError, enableLog) - - // 清理缓存,根据不同的业务场景选择性地清理缓存 - const clearCache = (strategy: CacheInvalidationStrategy, context?: string): void => { - if (!cache) return - - let clearedCount = 0 - - switch (strategy) { - case CacheInvalidationStrategy.CLEAR_ALL: - cache.clear() - logger.log(`清空所有缓存 - ${context || ''}`) - break - - case CacheInvalidationStrategy.CLEAR_CURRENT: - clearedCount = cache.clearCurrentSearch(searchParams) - logger.log(`清空当前搜索缓存 ${clearedCount} 条 - ${context || ''}`) - break - - case CacheInvalidationStrategy.CLEAR_PAGINATION: - clearedCount = cache.clearPagination() - logger.log(`清空分页缓存 ${clearedCount} 条 - ${context || ''}`) - break - - case CacheInvalidationStrategy.KEEP_ALL: - default: - logger.log(`保持缓存不变 - ${context || ''}`) - break - } - // 手动触发缓存状态更新 - cacheUpdateTrigger.value++ - } - - // 获取数据的核心方法 - const fetchData = async ( - params?: Partial, - useCache = enableCache - ): Promise> => { - // 取消上一个请求 - if (abortController) { - abortController.abort() - } - - // 创建新的取消控制器 - const currentController = new AbortController() - abortController = currentController - - // 状态机:进入 loading 状态 - loadingState.value = 'loading' - error.value = null - - try { - let requestParams = Object.assign( - {}, - searchParams, - { - [pageKey]: pagination.current, - [sizeKey]: pagination.size - }, - params || {} - ) as TParams - - // 剔除不需要的参数 - if (excludeParams.length > 0) { - const filteredParams = { ...requestParams } - excludeParams.forEach((key) => { - delete (filteredParams as Record)[key] - }) - requestParams = filteredParams as TParams - } - - // 检查缓存 - if (useCache && cache) { - const cachedItem = cache.get(requestParams) - if (cachedItem) { - data.value = cachedItem.data - updatePaginationFromResponse(pagination, cachedItem.response) - - // 修复:避免重复设置相同的值,防止响应式循环更新 - const paramsRecord = searchParams as Record - if (paramsRecord[pageKey] !== pagination.current) { - paramsRecord[pageKey] = pagination.current - } - if (paramsRecord[sizeKey] !== pagination.size) { - paramsRecord[sizeKey] = pagination.size - } - - // 状态机:缓存命中,进入 success 状态 - loadingState.value = 'success' - - // 缓存命中时触发专门的回调,而不是 onSuccess - if (onCacheHit) { - onCacheHit(cachedItem.data, cachedItem.response) - } - - logger.log(`缓存命中`) - return cachedItem.response - } - } - - const response = await apiFn(requestParams) - - // 检查请求是否被取消 - if (currentController.signal.aborted) { - throw new Error('请求已取消') - } - - // 使用响应适配器转换为标准格式 - const standardResponse = responseAdapter(response) - - // 处理响应数据 - let tableData = extractTableData(standardResponse) - - // 应用数据转换函数 - if (dataTransformer) { - tableData = dataTransformer(tableData) - } - - // 更新状态 - data.value = tableData - updatePaginationFromResponse(pagination, standardResponse) - - // 修复:避免重复设置相同的值,防止响应式循环更新 - const paramsRecord = searchParams as Record - if (paramsRecord[pageKey] !== pagination.current) { - paramsRecord[pageKey] = pagination.current - } - if (paramsRecord[sizeKey] !== pagination.size) { - paramsRecord[sizeKey] = pagination.size - } - - // 缓存数据 - if (useCache && cache) { - cache.set(requestParams, tableData, standardResponse) - // 手动触发缓存状态更新 - cacheUpdateTrigger.value++ - logger.log(`数据已缓存`) - } - - // 状态机:请求成功,进入 success 状态 - loadingState.value = 'success' - - // 成功回调 - if (onSuccess) { - onSuccess(tableData, standardResponse) - } - - return standardResponse - } catch (err) { - if (err instanceof Error && err.message === '请求已取消') { - // 请求被取消,回到 idle 状态 - loadingState.value = 'idle' - return { records: [], total: 0, current: 1, size: 10 } - } - - // 状态机:请求失败,进入 error 状态 - loadingState.value = 'error' - data.value = [] - const tableError = handleError(err, '获取表格数据失败') - throw tableError - } finally { - // 只有当前控制器是活跃的才清空 - if (abortController === currentController) { - abortController = null - } - } - } - - // 获取数据 (保持当前页) - const getData = async (params?: Partial): Promise | void> => { - try { - return await fetchData(params) - } catch { - // 错误已在 fetchData 中处理 - return Promise.resolve() - } - } - - // 分页获取数据 (重置到第一页) - 专门用于搜索场景 - const getDataByPage = async (params?: Partial): Promise | void> => { - pagination.current = 1 - ;(searchParams as Record)[pageKey] = 1 - - // 搜索时清空当前搜索条件的缓存,确保获取最新数据 - clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '搜索数据') - - try { - return await fetchData(params, false) // 搜索时不使用缓存 - } catch { - // 错误已在 fetchData 中处理 - return Promise.resolve() - } - } - - // 智能防抖搜索函数 - const debouncedGetDataByPage = createSmartDebounce(getDataByPage, debounceTime) - - // 重置搜索参数 - const resetSearchParams = async (): Promise => { - // 取消防抖的搜索 - debouncedGetDataByPage.cancel() - - // 保存分页相关的默认值 - const paramsRecord = searchParams as Record - const defaultPagination = { - [pageKey]: 1, - [sizeKey]: (paramsRecord[sizeKey] as number) || 10 - } - - // 清空所有搜索参数 - Object.keys(searchParams).forEach((key) => { - delete paramsRecord[key] - }) - - // 重新设置默认参数 - Object.assign(searchParams, apiParams || {}, defaultPagination) - - // 重置分页 - pagination.current = 1 - pagination.size = defaultPagination[sizeKey] as number - - // 清空错误状态 - error.value = null - - // 清空缓存 - clearCache(CacheInvalidationStrategy.CLEAR_ALL, '重置搜索') - - // 重新获取数据 - await getData() - - // 执行重置回调 - if (resetFormCallback) { - await nextTick() - resetFormCallback() - } - } - - // 防重复调用的标志 - let isCurrentChanging = false - - // 处理分页大小变化 - const handleSizeChange = async (newSize: number): Promise => { - if (newSize <= 0) return - - debouncedGetDataByPage.cancel() - - const paramsRecord = searchParams as Record - pagination.size = newSize - pagination.current = 1 - paramsRecord[sizeKey] = newSize - paramsRecord[pageKey] = 1 - - clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '分页大小变化') - - await getData() - } - - // 处理当前页变化 - const handleCurrentChange = async (newCurrent: number): Promise => { - if (newCurrent <= 0) return - - // 修复:防止重复调用 - if (isCurrentChanging) { - return - } - - // 修复:如果当前页没有变化,不需要重新请求 - if (pagination.current === newCurrent) { - logger.log('分页页码未变化,跳过请求') - return - } - - try { - isCurrentChanging = true - - // 修复:只更新必要的状态 - const paramsRecord = searchParams as Record - pagination.current = newCurrent - // 只有当 searchParams 的分页字段与新值不同时才更新 - if (paramsRecord[pageKey] !== newCurrent) { - paramsRecord[pageKey] = newCurrent - } - - await getData() - } finally { - isCurrentChanging = false - } - } - - // 针对不同业务场景的刷新方法 - - // 新增后刷新:回到第一页并清空分页缓存(适用于新增数据后) - const refreshCreate = async (): Promise => { - debouncedGetDataByPage.cancel() - pagination.current = 1 - ;(searchParams as Record)[pageKey] = 1 - clearCache(CacheInvalidationStrategy.CLEAR_PAGINATION, '新增数据') - await getData() - } - - // 更新后刷新:保持当前页,仅清空当前搜索缓存(适用于更新数据后) - const refreshUpdate = async (): Promise => { - clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '编辑数据') - await getData() - } - - // 删除后刷新:智能处理页码,避免空页面(适用于删除数据后) - const refreshRemove = async (): Promise => { - const { current } = pagination - - // 清除缓存并获取最新数据 - clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '删除数据') - await getData() - - // 如果当前页为空且不是第一页,回到上一页 - if (data.value.length === 0 && current > 1) { - pagination.current = current - 1 - ;(searchParams as Record)[pageKey] = current - 1 - await getData() - } - } - - // 全量刷新:清空所有缓存,重新获取数据(适用于手动刷新按钮) - const refreshData = async (): Promise => { - debouncedGetDataByPage.cancel() - clearCache(CacheInvalidationStrategy.CLEAR_ALL, '手动刷新') - await getData() - } - - // 轻量刷新:仅清空当前搜索条件的缓存,保持分页状态(适用于定时刷新) - const refreshSoft = async (): Promise => { - clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '软刷新') - await getData() - } - - // 取消当前请求 - const cancelRequest = (): void => { - if (abortController) { - abortController.abort() - } - debouncedGetDataByPage.cancel() - } - - // 清空数据 - const clearData = (): void => { - data.value = [] - error.value = null - clearCache(CacheInvalidationStrategy.CLEAR_ALL, '清空数据') - } - - // 清理已过期的缓存条目,释放内存空间 - const clearExpiredCache = (): number => { - if (!cache) return 0 - const cleanedCount = cache.cleanupExpired() - if (cleanedCount > 0) { - // 手动触发缓存状态更新 - cacheUpdateTrigger.value++ - } - return cleanedCount - } - - // 设置定期清理过期缓存 - if (enableCache && cache) { - cacheCleanupTimer = setInterval(() => { - const cleanedCount = cache.cleanupExpired() - if (cleanedCount > 0) { - logger.log(`自动清理 ${cleanedCount} 条过期缓存`) - // 手动触发缓存状态更新 - cacheUpdateTrigger.value++ - } - }, cacheTime / 2) // 每半个缓存周期清理一次 - } - - // 挂载时自动加载数据 - if (immediate) { - onMounted(async () => { - await getData() - }) - } - - // 组件卸载时彻底清理 - onUnmounted(() => { - cancelRequest() - if (cache) { - cache.clear() - } - if (cacheCleanupTimer) { - clearInterval(cacheCleanupTimer) - } - }) - - // 优化的返回值结构 - return { - // 数据相关 - /** 表格数据 */ - data, - /** 数据加载状态 */ - loading: readonly(loading), - /** 错误状态 */ - error: readonly(error), - /** 数据是否为空 */ - isEmpty: computed(() => data.value.length === 0), - /** 是否有数据 */ - hasData, - - // 分页相关 - /** 分页状态信息 */ - pagination: readonly(pagination), - /** 移动端分页配置 */ - paginationMobile: mobilePagination, - /** 页面大小变化处理 */ - handleSizeChange, - /** 当前页变化处理 */ - handleCurrentChange, - - // 搜索相关 - 统一前缀 - /** 搜索参数 */ - searchParams, - /** 重置搜索参数 */ - resetSearchParams, - - // 数据操作 - 更明确的操作意图 - /** 加载数据 */ - fetchData: getData, - /** 获取数据 */ - getData: getDataByPage, - /** 获取数据(防抖) */ - getDataDebounced: debouncedGetDataByPage, - /** 清空数据 */ - clearData, - - // 刷新策略 - /** 全量刷新:清空所有缓存,重新获取数据(适用于手动刷新按钮) */ - refreshData, - /** 轻量刷新:仅清空当前搜索条件的缓存,保持分页状态(适用于定时刷新) */ - refreshSoft, - /** 新增后刷新:回到第一页并清空分页缓存(适用于新增数据后) */ - refreshCreate, - /** 更新后刷新:保持当前页,仅清空当前搜索缓存(适用于更新数据后) */ - refreshUpdate, - /** 删除后刷新:智能处理页码,避免空页面(适用于删除数据后) */ - refreshRemove, - - // 缓存控制 - /** 缓存统计信息 */ - cacheInfo, - /** 清除缓存,根据不同的业务场景选择性地清理缓存: */ - clearCache, - // 支持4种清理策略 - // clearCache(CacheInvalidationStrategy.CLEAR_ALL, '手动刷新') // 清空所有缓存 - // clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '搜索数据') // 只清空当前搜索条件的缓存 - // clearCache(CacheInvalidationStrategy.CLEAR_PAGINATION, '新增数据') // 清空分页相关缓存 - // clearCache(CacheInvalidationStrategy.KEEP_ALL, '保持缓存') // 不清理任何缓存 - /** 清理已过期的缓存条目,释放内存空间 */ - clearExpiredCache, - - // 请求控制 - /** 取消当前请求 */ - cancelRequest, - - // 列配置 (如果提供了 columnsFactory) - ...(columnConfig && { - /** 表格列配置 */ - columns, - /** 列显示控制 */ - columnChecks, - /** 新增列 */ - addColumn: columnConfig.addColumn, - /** 删除列 */ - removeColumn: columnConfig.removeColumn, - /** 切换列显示状态 */ - toggleColumn: columnConfig.toggleColumn, - /** 更新列配置 */ - updateColumn: columnConfig.updateColumn, - /** 批量更新列配置 */ - batchUpdateColumns: columnConfig.batchUpdateColumns, - /** 重新排序列 */ - reorderColumns: columnConfig.reorderColumns, - /** 获取指定列配置 */ - getColumnConfig: columnConfig.getColumnConfig, - /** 获取所有列配置 */ - getAllColumns: columnConfig.getAllColumns, - /** 重置所有列配置到默认状态 */ - resetColumns: columnConfig.resetColumns - }) - } + type TRecord = InferRecordType> + type TParams = InferApiParams + const { + core: { + apiFn, + apiParams = {} as Partial, + excludeParams = [], + immediate = true, + columnsFactory, + paginationKey + }, + transform: { dataTransformer, responseAdapter = defaultResponseAdapter } = {}, + performance: { + enableCache = false, + cacheTime = 5 * 60 * 1000, + debounceTime = 300, + maxCacheSize = 50 + } = {}, + hooks: { onSuccess, onError, onCacheHit, resetFormCallback } = {}, + debug: { enableLog = false } = {} + } = config + + // 分页字段名配置:优先使用传入的配置,否则使用全局配置 + const pageKey = paginationKey?.current || tableConfig.paginationKey.current + const sizeKey = paginationKey?.size || tableConfig.paginationKey.size + + // 响应式触发器,用于手动更新缓存统计信息 + const cacheUpdateTrigger = ref(0) + + // 日志工具函数 + const logger = { + log: (message: string, ...args: unknown[]) => { + if (enableLog) { + console.log(`[useTable] ${message}`, ...args) + } + }, + warn: (message: string, ...args: unknown[]) => { + if (enableLog) { + console.warn(`[useTable] ${message}`, ...args) + } + }, + error: (message: string, ...args: unknown[]) => { + if (enableLog) { + console.error(`[useTable] ${message}`, ...args) + } + } + } + + // 缓存实例 + const cache = enableCache ? new TableCache(cacheTime, maxCacheSize, enableLog) : null + + // 加载状态机 + type LoadingState = 'idle' | 'loading' | 'success' | 'error' + const loadingState = ref('idle') + const loading = computed(() => loadingState.value === 'loading') + + // 错误状态 + const error = ref(null) + + // 表格数据 + const data = ref([]) + + // 请求取消控制器 + let abortController: AbortController | null = null + + // 缓存清理定时器 + let cacheCleanupTimer: NodeJS.Timeout | null = null + + // 搜索参数 + const searchParams = reactive( + Object.assign( + { + [pageKey]: 1, + [sizeKey]: 10 + }, + apiParams || {} + ) as TParams + ) + + // 分页配置 + const pagination = reactive({ + current: ((searchParams as Record)[pageKey] as number) || 1, + size: ((searchParams as Record)[sizeKey] as number) || 10, + total: 0 + }) + + // 移动端分页 (响应式) + const { width } = useWindowSize() + const mobilePagination = computed(() => ({ + ...pagination, + small: width.value < 768 + })) + + // 列配置 + const columnConfig = columnsFactory ? useTableColumns(columnsFactory) : null + const columns = columnConfig?.columns + const columnChecks = columnConfig?.columnChecks + + // 是否有数据 + const hasData = computed(() => data.value.length > 0) + + // 缓存统计信息 + const cacheInfo = computed(() => { + // 依赖触发器,确保缓存变化时重新计算 + void cacheUpdateTrigger.value + if (!cache) return { total: 0, size: '0KB', hitRate: '0 avg hits' } + return cache.getStats() + }) + + // 错误处理函数 + const handleError = createErrorHandler(onError, enableLog) + + // 清理缓存,根据不同的业务场景选择性地清理缓存 + const clearCache = (strategy: CacheInvalidationStrategy, context?: string): void => { + if (!cache) return + + let clearedCount = 0 + + switch (strategy) { + case CacheInvalidationStrategy.CLEAR_ALL: + cache.clear() + logger.log(`清空所有缓存 - ${context || ''}`) + break + + case CacheInvalidationStrategy.CLEAR_CURRENT: + clearedCount = cache.clearCurrentSearch(searchParams) + logger.log(`清空当前搜索缓存 ${clearedCount} 条 - ${context || ''}`) + break + + case CacheInvalidationStrategy.CLEAR_PAGINATION: + clearedCount = cache.clearPagination() + logger.log(`清空分页缓存 ${clearedCount} 条 - ${context || ''}`) + break + + case CacheInvalidationStrategy.KEEP_ALL: + default: + logger.log(`保持缓存不变 - ${context || ''}`) + break + } + // 手动触发缓存状态更新 + cacheUpdateTrigger.value++ + } + + // 获取数据的核心方法 + const fetchData = async ( + params?: Partial, + useCache = enableCache + ): Promise> => { + // 取消上一个请求 + if (abortController) { + abortController.abort() + } + + // 创建新的取消控制器 + const currentController = new AbortController() + abortController = currentController + + // 状态机:进入 loading 状态 + loadingState.value = 'loading' + error.value = null + + try { + let requestParams = Object.assign( + {}, + searchParams, + { + [pageKey]: pagination.current, + [sizeKey]: pagination.size + }, + params || {} + ) as TParams + + // 剔除不需要的参数 + if (excludeParams.length > 0) { + const filteredParams = { ...requestParams } + excludeParams.forEach((key) => { + delete (filteredParams as Record)[key] + }) + requestParams = filteredParams as TParams + } + + // 检查缓存 + if (useCache && cache) { + const cachedItem = cache.get(requestParams) + if (cachedItem) { + data.value = cachedItem.data + updatePaginationFromResponse(pagination, cachedItem.response) + + // 修复:避免重复设置相同的值,防止响应式循环更新 + const paramsRecord = searchParams as Record + if (paramsRecord[pageKey] !== pagination.current) { + paramsRecord[pageKey] = pagination.current + } + if (paramsRecord[sizeKey] !== pagination.size) { + paramsRecord[sizeKey] = pagination.size + } + + // 状态机:缓存命中,进入 success 状态 + loadingState.value = 'success' + + // 缓存命中时触发专门的回调,而不是 onSuccess + if (onCacheHit) { + onCacheHit(cachedItem.data, cachedItem.response) + } + + logger.log(`缓存命中`) + return cachedItem.response + } + } + + const response = await apiFn(requestParams) + + // 检查请求是否被取消 + if (currentController.signal.aborted) { + throw new Error('请求已取消') + } + + // 使用响应适配器转换为标准格式 + const standardResponse = responseAdapter(response) + + // 处理响应数据 + let tableData = extractTableData(standardResponse) + + // 应用数据转换函数 + if (dataTransformer) { + tableData = dataTransformer(tableData) + } + + // 更新状态 + data.value = tableData + updatePaginationFromResponse(pagination, standardResponse) + + // 修复:避免重复设置相同的值,防止响应式循环更新 + const paramsRecord = searchParams as Record + if (paramsRecord[pageKey] !== pagination.current) { + paramsRecord[pageKey] = pagination.current + } + if (paramsRecord[sizeKey] !== pagination.size) { + paramsRecord[sizeKey] = pagination.size + } + + // 缓存数据 + if (useCache && cache) { + cache.set(requestParams, tableData, standardResponse) + // 手动触发缓存状态更新 + cacheUpdateTrigger.value++ + logger.log(`数据已缓存`) + } + + // 状态机:请求成功,进入 success 状态 + loadingState.value = 'success' + + // 成功回调 + if (onSuccess) { + onSuccess(tableData, standardResponse) + } + + return standardResponse + } catch (err) { + if (err instanceof Error && err.message === '请求已取消') { + // 请求被取消,回到 idle 状态 + loadingState.value = 'idle' + return { records: [], total: 0, current: 1, size: 10 } + } + + // 状态机:请求失败,进入 error 状态 + loadingState.value = 'error' + data.value = [] + const tableError = handleError(err, '获取表格数据失败') + throw tableError + } finally { + // 只有当前控制器是活跃的才清空 + if (abortController === currentController) { + abortController = null + } + } + } + + // 获取数据 (保持当前页) + const getData = async (params?: Partial): Promise | void> => { + try { + return await fetchData(params) + } catch { + // 错误已在 fetchData 中处理 + return Promise.resolve() + } + } + + // 分页获取数据 (重置到第一页) - 专门用于搜索场景 + const getDataByPage = async ( + params?: Partial + ): Promise | void> => { + pagination.current = 1 + ;(searchParams as Record)[pageKey] = 1 + + // 搜索时清空当前搜索条件的缓存,确保获取最新数据 + clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '搜索数据') + + try { + return await fetchData(params, false) // 搜索时不使用缓存 + } catch { + // 错误已在 fetchData 中处理 + return Promise.resolve() + } + } + + // 智能防抖搜索函数 + const debouncedGetDataByPage = createSmartDebounce(getDataByPage, debounceTime) + + // 重置搜索参数 + const resetSearchParams = async (): Promise => { + // 取消防抖的搜索 + debouncedGetDataByPage.cancel() + + // 保存分页相关的默认值 + const paramsRecord = searchParams as Record + const defaultPagination = { + [pageKey]: 1, + [sizeKey]: (paramsRecord[sizeKey] as number) || 10 + } + + // 清空所有搜索参数 + Object.keys(searchParams).forEach((key) => { + delete paramsRecord[key] + }) + + // 重新设置默认参数 + Object.assign(searchParams, apiParams || {}, defaultPagination) + + // 重置分页 + pagination.current = 1 + pagination.size = defaultPagination[sizeKey] as number + + // 清空错误状态 + error.value = null + + // 清空缓存 + clearCache(CacheInvalidationStrategy.CLEAR_ALL, '重置搜索') + + // 重新获取数据 + await getData() + + // 执行重置回调 + if (resetFormCallback) { + await nextTick() + resetFormCallback() + } + } + + // 防重复调用的标志 + let isCurrentChanging = false + + // 处理分页大小变化 + const handleSizeChange = async (newSize: number): Promise => { + if (newSize <= 0) return + + debouncedGetDataByPage.cancel() + + const paramsRecord = searchParams as Record + pagination.size = newSize + pagination.current = 1 + paramsRecord[sizeKey] = newSize + paramsRecord[pageKey] = 1 + + clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '分页大小变化') + + await getData() + } + + // 处理当前页变化 + const handleCurrentChange = async (newCurrent: number): Promise => { + if (newCurrent <= 0) return + + // 修复:防止重复调用 + if (isCurrentChanging) { + return + } + + // 修复:如果当前页没有变化,不需要重新请求 + if (pagination.current === newCurrent) { + logger.log('分页页码未变化,跳过请求') + return + } + + try { + isCurrentChanging = true + + // 修复:只更新必要的状态 + const paramsRecord = searchParams as Record + pagination.current = newCurrent + // 只有当 searchParams 的分页字段与新值不同时才更新 + if (paramsRecord[pageKey] !== newCurrent) { + paramsRecord[pageKey] = newCurrent + } + + await getData() + } finally { + isCurrentChanging = false + } + } + + // 针对不同业务场景的刷新方法 + + // 新增后刷新:回到第一页并清空分页缓存(适用于新增数据后) + const refreshCreate = async (): Promise => { + debouncedGetDataByPage.cancel() + pagination.current = 1 + ;(searchParams as Record)[pageKey] = 1 + clearCache(CacheInvalidationStrategy.CLEAR_PAGINATION, '新增数据') + await getData() + } + + // 更新后刷新:保持当前页,仅清空当前搜索缓存(适用于更新数据后) + const refreshUpdate = async (): Promise => { + clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '编辑数据') + await getData() + } + + // 删除后刷新:智能处理页码,避免空页面(适用于删除数据后) + const refreshRemove = async (): Promise => { + const { current } = pagination + + // 清除缓存并获取最新数据 + clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '删除数据') + await getData() + + // 如果当前页为空且不是第一页,回到上一页 + if (data.value.length === 0 && current > 1) { + pagination.current = current - 1 + ;(searchParams as Record)[pageKey] = current - 1 + await getData() + } + } + + // 全量刷新:清空所有缓存,重新获取数据(适用于手动刷新按钮) + const refreshData = async (): Promise => { + debouncedGetDataByPage.cancel() + clearCache(CacheInvalidationStrategy.CLEAR_ALL, '手动刷新') + await getData() + } + + // 轻量刷新:仅清空当前搜索条件的缓存,保持分页状态(适用于定时刷新) + const refreshSoft = async (): Promise => { + clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '软刷新') + await getData() + } + + // 取消当前请求 + const cancelRequest = (): void => { + if (abortController) { + abortController.abort() + } + debouncedGetDataByPage.cancel() + } + + // 清空数据 + const clearData = (): void => { + data.value = [] + error.value = null + clearCache(CacheInvalidationStrategy.CLEAR_ALL, '清空数据') + } + + // 清理已过期的缓存条目,释放内存空间 + const clearExpiredCache = (): number => { + if (!cache) return 0 + const cleanedCount = cache.cleanupExpired() + if (cleanedCount > 0) { + // 手动触发缓存状态更新 + cacheUpdateTrigger.value++ + } + return cleanedCount + } + + // 设置定期清理过期缓存 + if (enableCache && cache) { + cacheCleanupTimer = setInterval(() => { + const cleanedCount = cache.cleanupExpired() + if (cleanedCount > 0) { + logger.log(`自动清理 ${cleanedCount} 条过期缓存`) + // 手动触发缓存状态更新 + cacheUpdateTrigger.value++ + } + }, cacheTime / 2) // 每半个缓存周期清理一次 + } + + // 挂载时自动加载数据 + if (immediate) { + onMounted(async () => { + await getData() + }) + } + + // 组件卸载时彻底清理 + onUnmounted(() => { + cancelRequest() + if (cache) { + cache.clear() + } + if (cacheCleanupTimer) { + clearInterval(cacheCleanupTimer) + } + }) + + // 优化的返回值结构 + return { + // 数据相关 + /** 表格数据 */ + data, + /** 数据加载状态 */ + loading: readonly(loading), + /** 错误状态 */ + error: readonly(error), + /** 数据是否为空 */ + isEmpty: computed(() => data.value.length === 0), + /** 是否有数据 */ + hasData, + + // 分页相关 + /** 分页状态信息 */ + pagination: readonly(pagination), + /** 移动端分页配置 */ + paginationMobile: mobilePagination, + /** 页面大小变化处理 */ + handleSizeChange, + /** 当前页变化处理 */ + handleCurrentChange, + + // 搜索相关 - 统一前缀 + /** 搜索参数 */ + searchParams, + /** 重置搜索参数 */ + resetSearchParams, + + // 数据操作 - 更明确的操作意图 + /** 加载数据 */ + fetchData: getData, + /** 获取数据 */ + getData: getDataByPage, + /** 获取数据(防抖) */ + getDataDebounced: debouncedGetDataByPage, + /** 清空数据 */ + clearData, + + // 刷新策略 + /** 全量刷新:清空所有缓存,重新获取数据(适用于手动刷新按钮) */ + refreshData, + /** 轻量刷新:仅清空当前搜索条件的缓存,保持分页状态(适用于定时刷新) */ + refreshSoft, + /** 新增后刷新:回到第一页并清空分页缓存(适用于新增数据后) */ + refreshCreate, + /** 更新后刷新:保持当前页,仅清空当前搜索缓存(适用于更新数据后) */ + refreshUpdate, + /** 删除后刷新:智能处理页码,避免空页面(适用于删除数据后) */ + refreshRemove, + + // 缓存控制 + /** 缓存统计信息 */ + cacheInfo, + /** 清除缓存,根据不同的业务场景选择性地清理缓存: */ + clearCache, + // 支持4种清理策略 + // clearCache(CacheInvalidationStrategy.CLEAR_ALL, '手动刷新') // 清空所有缓存 + // clearCache(CacheInvalidationStrategy.CLEAR_CURRENT, '搜索数据') // 只清空当前搜索条件的缓存 + // clearCache(CacheInvalidationStrategy.CLEAR_PAGINATION, '新增数据') // 清空分页相关缓存 + // clearCache(CacheInvalidationStrategy.KEEP_ALL, '保持缓存') // 不清理任何缓存 + /** 清理已过期的缓存条目,释放内存空间 */ + clearExpiredCache, + + // 请求控制 + /** 取消当前请求 */ + cancelRequest, + + // 列配置 (如果提供了 columnsFactory) + ...(columnConfig && { + /** 表格列配置 */ + columns, + /** 列显示控制 */ + columnChecks, + /** 新增列 */ + addColumn: columnConfig.addColumn, + /** 删除列 */ + removeColumn: columnConfig.removeColumn, + /** 切换列显示状态 */ + toggleColumn: columnConfig.toggleColumn, + /** 更新列配置 */ + updateColumn: columnConfig.updateColumn, + /** 批量更新列配置 */ + batchUpdateColumns: columnConfig.batchUpdateColumns, + /** 重新排序列 */ + reorderColumns: columnConfig.reorderColumns, + /** 获取指定列配置 */ + getColumnConfig: columnConfig.getColumnConfig, + /** 获取所有列配置 */ + getAllColumns: columnConfig.getAllColumns, + /** 重置所有列配置到默认状态 */ + resetColumns: columnConfig.resetColumns + }) + } } // 重新导出类型和枚举,方便使用 diff --git a/src/hooks/core/useTableColumns.ts b/src/hooks/core/useTableColumns.ts index 84b6e13..9ad8592 100644 --- a/src/hooks/core/useTableColumns.ts +++ b/src/hooks/core/useTableColumns.ts @@ -40,273 +40,286 @@ import type { ColumnOption } from '@/types/component' * 特殊列类型 */ const SPECIAL_COLUMNS: Record = { - selection: { prop: '__selection__', label: $t('table.column.selection') }, - expand: { prop: '__expand__', label: $t('table.column.expand') }, - index: { prop: '__index__', label: $t('table.column.index') } + selection: { prop: '__selection__', label: $t('table.column.selection') }, + expand: { prop: '__expand__', label: $t('table.column.expand') }, + index: { prop: '__index__', label: $t('table.column.index') } } /** * 获取列的唯一标识 */ export const getColumnKey = (col: ColumnOption) => - SPECIAL_COLUMNS[col.type as keyof typeof SPECIAL_COLUMNS]?.prop ?? (col.prop as string) + SPECIAL_COLUMNS[col.type as keyof typeof SPECIAL_COLUMNS]?.prop ?? (col.prop as string) /** * 获取列的显示状态 * 优先使用 visible 字段,如果不存在则使用 checked 字段 */ export const getColumnVisibility = (col: ColumnOption): boolean => { - // visible 优先级高于 checked - if (col.visible !== undefined) { - return col.visible - } - // 如果 visible 未定义,使用 checked,默认为 true - return col.checked ?? true + // visible 优先级高于 checked + if (col.visible !== undefined) { + return col.visible + } + // 如果 visible 未定义,使用 checked,默认为 true + return col.checked ?? true } /** * 获取列的检查状态 */ export const getColumnChecks = (columns: ColumnOption[]) => - columns.map((col) => { - const special = col.type && SPECIAL_COLUMNS[col.type] - const visibility = getColumnVisibility(col) + columns.map((col) => { + const special = col.type && SPECIAL_COLUMNS[col.type] + const visibility = getColumnVisibility(col) - if (special) { - return { ...col, prop: special.prop, label: special.label, checked: true, visible: true } - } - return { ...col, checked: visibility, visible: visibility } - }) + if (special) { + return { + ...col, + prop: special.prop, + label: special.label, + checked: true, + visible: true + } + } + return { ...col, checked: visibility, visible: visibility } + }) /** * 动态列配置接口 */ export interface DynamicColumnConfig { - /** - * 新增列(支持单个或批量) - * @param column 列配置或列配置数组 - * @param index 可选的插入位置,默认末尾(批量时为第一个列的位置) - */ - addColumn: (column: ColumnOption | ColumnOption[], index?: number) => void - /** - * 删除列(支持单个或批量) - * @param prop 列的唯一标识或标识数组 - */ - removeColumn: (prop: string | string[]) => void - /** - * 切换列显示状态(支持单个或批量) - * @param prop 列的唯一标识或标识数组 - * @param visible 可选的显示状态,默认取反 - */ - toggleColumn: (prop: string | string[], visible?: boolean) => void + /** + * 新增列(支持单个或批量) + * @param column 列配置或列配置数组 + * @param index 可选的插入位置,默认末尾(批量时为第一个列的位置) + */ + addColumn: (column: ColumnOption | ColumnOption[], index?: number) => void + /** + * 删除列(支持单个或批量) + * @param prop 列的唯一标识或标识数组 + */ + removeColumn: (prop: string | string[]) => void + /** + * 切换列显示状态(支持单个或批量) + * @param prop 列的唯一标识或标识数组 + * @param visible 可选的显示状态,默认取反 + */ + toggleColumn: (prop: string | string[], visible?: boolean) => void - /** - * 更新列(支持单个或批量) - * @param prop 列的唯一标识或更新配置数组 - * @param updates 列配置更新(当 prop 为字符串时使用) - */ - updateColumn: ( - prop: string | Array<{ prop: string; updates: Partial> }>, - updates?: Partial> - ) => void - /** - * 批量更新列(兼容旧版本,推荐使用 updateColumn 的数组模式) - * @param updates 列更新配置 - * @deprecated 推荐使用 updateColumn 的数组模式 - */ - batchUpdateColumns: (updates: Array<{ prop: string; updates: Partial> }>) => void - /** - * 重新排序列 - * @param fromIndex 源索引 - * @param toIndex 目标索引 - */ - reorderColumns: (fromIndex: number, toIndex: number) => void - /** - * 获取列配置 - * @param prop 列的唯一标识 - * @returns 列配置 - */ - getColumnConfig: (prop: string) => ColumnOption | undefined - /** - * 获取所有列配置 - * @returns 所有列配置 - */ - getAllColumns: () => ColumnOption[] - /** - * 重置所有列 - */ - resetColumns: () => void + /** + * 更新列(支持单个或批量) + * @param prop 列的唯一标识或更新配置数组 + * @param updates 列配置更新(当 prop 为字符串时使用) + */ + updateColumn: ( + prop: string | Array<{ prop: string; updates: Partial> }>, + updates?: Partial> + ) => void + /** + * 批量更新列(兼容旧版本,推荐使用 updateColumn 的数组模式) + * @param updates 列更新配置 + * @deprecated 推荐使用 updateColumn 的数组模式 + */ + batchUpdateColumns: ( + updates: Array<{ prop: string; updates: Partial> }> + ) => void + /** + * 重新排序列 + * @param fromIndex 源索引 + * @param toIndex 目标索引 + */ + reorderColumns: (fromIndex: number, toIndex: number) => void + /** + * 获取列配置 + * @param prop 列的唯一标识 + * @returns 列配置 + */ + getColumnConfig: (prop: string) => ColumnOption | undefined + /** + * 获取所有列配置 + * @returns 所有列配置 + */ + getAllColumns: () => ColumnOption[] + /** + * 重置所有列 + */ + resetColumns: () => void } export function useTableColumns( - columnsFactory: () => ColumnOption[] + columnsFactory: () => ColumnOption[] ): { - columns: any - columnChecks: any + columns: any + columnChecks: any } & DynamicColumnConfig { - const dynamicColumns = ref[]>(columnsFactory()) - const columnChecks = ref[]>(getColumnChecks(dynamicColumns.value)) + const dynamicColumns = ref[]>(columnsFactory()) + const columnChecks = ref[]>(getColumnChecks(dynamicColumns.value)) - // 当 dynamicColumns 变动时,重新生成 columnChecks 且保留已存在的显示状态 - watch( - dynamicColumns, - (newCols) => { - const visibilityMap = new Map( - columnChecks.value.map((c) => [getColumnKey(c), getColumnVisibility(c)]) - ) - const newChecks = getColumnChecks(newCols).map((c) => { - const key = getColumnKey(c) - const visibility = visibilityMap.has(key) ? visibilityMap.get(key) : getColumnVisibility(c) - return { - ...c, - checked: visibility, - visible: visibility - } - }) - columnChecks.value = newChecks - }, - { deep: true } - ) + // 当 dynamicColumns 变动时,重新生成 columnChecks 且保留已存在的显示状态 + watch( + dynamicColumns, + (newCols) => { + const visibilityMap = new Map( + columnChecks.value.map((c) => [getColumnKey(c), getColumnVisibility(c)]) + ) + const newChecks = getColumnChecks(newCols).map((c) => { + const key = getColumnKey(c) + const visibility = visibilityMap.has(key) + ? visibilityMap.get(key) + : getColumnVisibility(c) + return { + ...c, + checked: visibility, + visible: visibility + } + }) + columnChecks.value = newChecks + }, + { deep: true } + ) - // 当前显示列(基于 columnChecks 的 checked 或 visible) - const columns = computed(() => { - const colMap = new Map(dynamicColumns.value.map((c) => [getColumnKey(c), c])) - return columnChecks.value - .filter((c) => getColumnVisibility(c)) - .map((c) => colMap.get(getColumnKey(c))) - .filter(Boolean) as ColumnOption[] - }) + // 当前显示列(基于 columnChecks 的 checked 或 visible) + const columns = computed(() => { + const colMap = new Map(dynamicColumns.value.map((c) => [getColumnKey(c), c])) + return columnChecks.value + .filter((c) => getColumnVisibility(c)) + .map((c) => colMap.get(getColumnKey(c))) + .filter(Boolean) as ColumnOption[] + }) - // 支持 updater 返回新数组或直接在传入数组上 mutate - const setDynamicColumns = (updater: (cols: ColumnOption[]) => void | ColumnOption[]) => { - const copy = [...dynamicColumns.value] - const result = updater(copy) - dynamicColumns.value = Array.isArray(result) ? result : copy - } + // 支持 updater 返回新数组或直接在传入数组上 mutate + const setDynamicColumns = (updater: (cols: ColumnOption[]) => void | ColumnOption[]) => { + const copy = [...dynamicColumns.value] + const result = updater(copy) + dynamicColumns.value = Array.isArray(result) ? result : copy + } - return { - columns, - columnChecks, + return { + columns, + columnChecks, - /** - * 新增列(支持单个或批量) - */ - addColumn: (column: ColumnOption | ColumnOption[], index?: number) => - setDynamicColumns((cols) => { - const next = [...cols] - const columnsToAdd = Array.isArray(column) ? column : [column] - const insertIndex = - typeof index === 'number' && index >= 0 && index <= next.length ? index : next.length + /** + * 新增列(支持单个或批量) + */ + addColumn: (column: ColumnOption | ColumnOption[], index?: number) => + setDynamicColumns((cols) => { + const next = [...cols] + const columnsToAdd = Array.isArray(column) ? column : [column] + const insertIndex = + typeof index === 'number' && index >= 0 && index <= next.length + ? index + : next.length - // 批量插入 - next.splice(insertIndex, 0, ...columnsToAdd) - return next - }), + // 批量插入 + next.splice(insertIndex, 0, ...columnsToAdd) + return next + }), - /** - * 删除列(支持单个或批量) - */ - removeColumn: (prop: string | string[]) => - setDynamicColumns((cols) => { - const propsToRemove = Array.isArray(prop) ? prop : [prop] - return cols.filter((c) => !propsToRemove.includes(getColumnKey(c))) - }), + /** + * 删除列(支持单个或批量) + */ + removeColumn: (prop: string | string[]) => + setDynamicColumns((cols) => { + const propsToRemove = Array.isArray(prop) ? prop : [prop] + return cols.filter((c) => !propsToRemove.includes(getColumnKey(c))) + }), - /** - * 更新列(支持单个或批量) - */ - updateColumn: ( - prop: string | Array<{ prop: string; updates: Partial> }>, - updates?: Partial> - ) => { - // 批量模式:prop 是数组 - if (Array.isArray(prop)) { - setDynamicColumns((cols) => { - const map = new Map(prop.map((u) => [u.prop, u.updates])) - return cols.map((c) => { - const key = getColumnKey(c) - const upd = map.get(key) - return upd ? { ...c, ...upd } : c - }) - }) - } - // 单个模式:prop 是字符串 - else if (updates) { - setDynamicColumns((cols) => - cols.map((c) => (getColumnKey(c) === prop ? { ...c, ...updates } : c)) - ) - } - }, + /** + * 更新列(支持单个或批量) + */ + updateColumn: ( + prop: string | Array<{ prop: string; updates: Partial> }>, + updates?: Partial> + ) => { + // 批量模式:prop 是数组 + if (Array.isArray(prop)) { + setDynamicColumns((cols) => { + const map = new Map(prop.map((u) => [u.prop, u.updates])) + return cols.map((c) => { + const key = getColumnKey(c) + const upd = map.get(key) + return upd ? { ...c, ...upd } : c + }) + }) + } + // 单个模式:prop 是字符串 + else if (updates) { + setDynamicColumns((cols) => + cols.map((c) => (getColumnKey(c) === prop ? { ...c, ...updates } : c)) + ) + } + }, - /** - * 切换列显示状态(支持单个或批量) - */ - toggleColumn: (prop: string | string[], visible?: boolean) => { - const propsToToggle = Array.isArray(prop) ? prop : [prop] - const next = [...columnChecks.value] + /** + * 切换列显示状态(支持单个或批量) + */ + toggleColumn: (prop: string | string[], visible?: boolean) => { + const propsToToggle = Array.isArray(prop) ? prop : [prop] + const next = [...columnChecks.value] - propsToToggle.forEach((p) => { - const i = next.findIndex((c) => getColumnKey(c) === p) - if (i > -1) { - const currentVisibility = getColumnVisibility(next[i]) - const newVisibility = visible ?? !currentVisibility - // 同时更新 checked 和 visible 以保持兼容性 - next[i] = { ...next[i], checked: newVisibility, visible: newVisibility } - } - }) + propsToToggle.forEach((p) => { + const i = next.findIndex((c) => getColumnKey(c) === p) + if (i > -1) { + const currentVisibility = getColumnVisibility(next[i]) + const newVisibility = visible ?? !currentVisibility + // 同时更新 checked 和 visible 以保持兼容性 + next[i] = { ...next[i], checked: newVisibility, visible: newVisibility } + } + }) - columnChecks.value = next - }, + columnChecks.value = next + }, - /** - * 重置所有列 - */ - resetColumns: () => { - dynamicColumns.value = columnsFactory() - }, + /** + * 重置所有列 + */ + resetColumns: () => { + dynamicColumns.value = columnsFactory() + }, - /** - * 批量更新列(兼容旧版本) - * @deprecated 推荐使用 updateColumn 的数组模式 - */ - batchUpdateColumns: (updates) => - setDynamicColumns((cols) => { - const map = new Map(updates.map((u) => [u.prop, u.updates])) - return cols.map((c) => { - const key = getColumnKey(c) - const upd = map.get(key) - return upd ? { ...c, ...upd } : c - }) - }), + /** + * 批量更新列(兼容旧版本) + * @deprecated 推荐使用 updateColumn 的数组模式 + */ + batchUpdateColumns: (updates) => + setDynamicColumns((cols) => { + const map = new Map(updates.map((u) => [u.prop, u.updates])) + return cols.map((c) => { + const key = getColumnKey(c) + const upd = map.get(key) + return upd ? { ...c, ...upd } : c + }) + }), - /** - * 重新排序列 - */ - reorderColumns: (fromIndex: number, toIndex: number) => - setDynamicColumns((cols) => { - if ( - fromIndex < 0 || - fromIndex >= cols.length || - toIndex < 0 || - toIndex >= cols.length || - fromIndex === toIndex - ) { - return cols - } - const next = [...cols] - const [moved] = next.splice(fromIndex, 1) - next.splice(toIndex, 0, moved) - return next - }), + /** + * 重新排序列 + */ + reorderColumns: (fromIndex: number, toIndex: number) => + setDynamicColumns((cols) => { + if ( + fromIndex < 0 || + fromIndex >= cols.length || + toIndex < 0 || + toIndex >= cols.length || + fromIndex === toIndex + ) { + return cols + } + const next = [...cols] + const [moved] = next.splice(fromIndex, 1) + next.splice(toIndex, 0, moved) + return next + }), - /** - * 获取列配置 - */ - getColumnConfig: (prop: string) => dynamicColumns.value.find((c) => getColumnKey(c) === prop), + /** + * 获取列配置 + */ + getColumnConfig: (prop: string) => + dynamicColumns.value.find((c) => getColumnKey(c) === prop), - /** - * 获取所有列配置 - */ - getAllColumns: () => [...dynamicColumns.value] - } + /** + * 获取所有列配置 + */ + getAllColumns: () => [...dynamicColumns.value] + } } diff --git a/src/hooks/core/useTableHeight.ts b/src/hooks/core/useTableHeight.ts index 8fdf6da..7e2d068 100644 --- a/src/hooks/core/useTableHeight.ts +++ b/src/hooks/core/useTableHeight.ts @@ -21,64 +21,67 @@ import { computed, type Ref } from 'vue' * 表格高度计算器配置接口 */ interface TableHeightOptions { - /** 是否显示表格头部 */ - showTableHeader: Ref - /** 分页器高度 */ - paginationHeight: Ref - /** 表格头部高度 */ - tableHeaderHeight: Ref - /** 分页器间距 */ - paginationSpacing: Ref + /** 是否显示表格头部 */ + showTableHeader: Ref + /** 分页器高度 */ + paginationHeight: Ref + /** 表格头部高度 */ + tableHeaderHeight: Ref + /** 分页器间距 */ + paginationSpacing: Ref } /** * 表格高度计算器类 */ class TableHeightCalculator { - // 常量配置 - private static readonly DEFAULT_TABLE_HEADER_HEIGHT = 44 - private static readonly TABLE_HEADER_SPACING = 12 + // 常量配置 + private static readonly DEFAULT_TABLE_HEADER_HEIGHT = 44 + private static readonly TABLE_HEADER_SPACING = 12 - constructor(private options: TableHeightOptions) {} + constructor(private options: TableHeightOptions) {} - /** - * 计算容器高度 - */ - calculate(): { height: string } { - const offset = this.calculateOffset() - return { - height: offset === 0 ? '100%' : `calc(100% - ${offset}px)` - } - } + /** + * 计算容器高度 + */ + calculate(): { height: string } { + const offset = this.calculateOffset() + return { + height: offset === 0 ? '100%' : `calc(100% - ${offset}px)` + } + } - /** - * 计算偏移量 - */ - private calculateOffset(): number { - if (!this.options.showTableHeader.value) { - return this.calculatePaginationOffset() - } + /** + * 计算偏移量 + */ + private calculateOffset(): number { + if (!this.options.showTableHeader.value) { + return this.calculatePaginationOffset() + } - const headerHeight = this.getHeaderHeight() - const paginationOffset = this.calculatePaginationOffset() + const headerHeight = this.getHeaderHeight() + const paginationOffset = this.calculatePaginationOffset() - return headerHeight + paginationOffset + TableHeightCalculator.TABLE_HEADER_SPACING - } + return headerHeight + paginationOffset + TableHeightCalculator.TABLE_HEADER_SPACING + } - /** - * 获取表格头部高度 - */ - private getHeaderHeight(): number { - return this.options.tableHeaderHeight.value || TableHeightCalculator.DEFAULT_TABLE_HEADER_HEIGHT - } + /** + * 获取表格头部高度 + */ + private getHeaderHeight(): number { + return ( + this.options.tableHeaderHeight.value || + TableHeightCalculator.DEFAULT_TABLE_HEADER_HEIGHT + ) + } - /** - * 计算分页器偏移量 - */ - private calculatePaginationOffset(): number { - const { paginationHeight, paginationSpacing } = this.options - return paginationHeight.value === 0 ? 0 : paginationHeight.value + paginationSpacing.value - } + /** + * 计算分页器偏移量 + */ + private calculatePaginationOffset(): number { + const { paginationHeight, paginationSpacing } = this.options + return paginationHeight.value === 0 ? 0 : paginationHeight.value + paginationSpacing.value + } } /** @@ -93,13 +96,13 @@ class TableHeightCalculator { * @returns 容器高度计算结果 */ export function useTableHeight(options: TableHeightOptions) { - const containerHeight = computed(() => { - const calculator = new TableHeightCalculator(options) - return calculator.calculate() - }) + const containerHeight = computed(() => { + const calculator = new TableHeightCalculator(options) + return calculator.calculate() + }) - return { - /** 容器高度样式对象 */ - containerHeight - } + return { + /** 容器高度样式对象 */ + containerHeight + } } diff --git a/src/hooks/core/useTheme.ts b/src/hooks/core/useTheme.ts index 187c3e0..a6a9295 100644 --- a/src/hooks/core/useTheme.ts +++ b/src/hooks/core/useTheme.ts @@ -40,135 +40,138 @@ import { usePreferredDark } from '@vueuse/core' import { watch } from 'vue' export function useTheme() { - const settingStore = useSettingStore() + const settingStore = useSettingStore() - // 禁用过渡效果 - const disableTransitions = () => { - const style = document.createElement('style') - style.setAttribute('id', 'disable-transitions') - style.textContent = '* { transition: none !important; }' - document.head.appendChild(style) - } + // 禁用过渡效果 + const disableTransitions = () => { + const style = document.createElement('style') + style.setAttribute('id', 'disable-transitions') + style.textContent = '* { transition: none !important; }' + document.head.appendChild(style) + } - // 启用过渡效果 - const enableTransitions = () => { - const style = document.getElementById('disable-transitions') - if (style) { - style.remove() - } - } + // 启用过渡效果 + const enableTransitions = () => { + const style = document.getElementById('disable-transitions') + if (style) { + style.remove() + } + } - // 设置系统主题 - const setSystemTheme = (theme: SystemThemeEnum, themeMode?: SystemThemeEnum) => { - // 临时禁用过渡效果 - disableTransitions() + // 设置系统主题 + const setSystemTheme = (theme: SystemThemeEnum, themeMode?: SystemThemeEnum) => { + // 临时禁用过渡效果 + disableTransitions() - const el = document.getElementsByTagName('html')[0] - const isDark = theme === SystemThemeEnum.DARK + const el = document.getElementsByTagName('html')[0] + const isDark = theme === SystemThemeEnum.DARK - if (!themeMode) { - themeMode = theme - } + if (!themeMode) { + themeMode = theme + } - const currentTheme = AppConfig.systemThemeStyles[theme as keyof SystemThemeTypes] + const currentTheme = AppConfig.systemThemeStyles[theme as keyof SystemThemeTypes] - if (currentTheme) { - el.setAttribute('class', currentTheme.className) - } + if (currentTheme) { + el.setAttribute('class', currentTheme.className) + } - // 设置按钮颜色加深或变浅 - const primary = settingStore.systemThemeColor + // 设置按钮颜色加深或变浅 + const primary = settingStore.systemThemeColor - for (let i = 1; i <= 9; i++) { - document.documentElement.style.setProperty( - `--el-color-primary-light-${i}`, - isDark ? `${getDarkColor(primary, i / 10)}` : `${getLightColor(primary, i / 10)}` - ) - } + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty( + `--el-color-primary-light-${i}`, + isDark ? `${getDarkColor(primary, i / 10)}` : `${getLightColor(primary, i / 10)}` + ) + } - // 更新store中的主题设置 - settingStore.setGlopTheme(theme, themeMode) + // 更新store中的主题设置 + settingStore.setGlopTheme(theme, themeMode) - // 使用 requestAnimationFrame 确保在下一帧恢复过渡效果 - requestAnimationFrame(() => { - requestAnimationFrame(() => { - enableTransitions() - }) - }) - } + // 使用 requestAnimationFrame 确保在下一帧恢复过渡效果 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + enableTransitions() + }) + }) + } - // 使用 VueUse 的 usePreferredDark 检测系统主题偏好 - const prefersDark = usePreferredDark() + // 使用 VueUse 的 usePreferredDark 检测系统主题偏好 + const prefersDark = usePreferredDark() - // 自动设置系统主题 - const setSystemAutoTheme = () => { - const theme = prefersDark.value ? SystemThemeEnum.DARK : SystemThemeEnum.LIGHT - setSystemTheme(theme, SystemThemeEnum.AUTO) - } + // 自动设置系统主题 + const setSystemAutoTheme = () => { + const theme = prefersDark.value ? SystemThemeEnum.DARK : SystemThemeEnum.LIGHT + setSystemTheme(theme, SystemThemeEnum.AUTO) + } - // 切换主题 - const switchThemeStyles = (theme: SystemThemeEnum) => { - if (theme === SystemThemeEnum.AUTO) { - setSystemAutoTheme() - } else { - setSystemTheme(theme) - } - } + // 切换主题 + const switchThemeStyles = (theme: SystemThemeEnum) => { + if (theme === SystemThemeEnum.AUTO) { + setSystemAutoTheme() + } else { + setSystemTheme(theme) + } + } - return { - setSystemTheme, - setSystemAutoTheme, - switchThemeStyles, - prefersDark - } + return { + setSystemTheme, + setSystemAutoTheme, + switchThemeStyles, + prefersDark + } } /** * 初始化主题系统 */ export function initializeTheme() { - const settingStore = useSettingStore() - const prefersDark = usePreferredDark() + const settingStore = useSettingStore() + const prefersDark = usePreferredDark() - // 根据系统偏好应用主题 - const applyThemeByMode = () => { - const el = document.getElementsByTagName('html')[0] - let actualTheme = settingStore.systemThemeType + // 根据系统偏好应用主题 + const applyThemeByMode = () => { + const el = document.getElementsByTagName('html')[0] + let actualTheme = settingStore.systemThemeType - // 如果是 AUTO 模式,检测系统偏好 - if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { - actualTheme = prefersDark.value ? SystemThemeEnum.DARK : SystemThemeEnum.LIGHT - // 更新实际应用的主题类型 - settingStore.systemThemeType = actualTheme - } + // 如果是 AUTO 模式,检测系统偏好 + if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { + actualTheme = prefersDark.value ? SystemThemeEnum.DARK : SystemThemeEnum.LIGHT + // 更新实际应用的主题类型 + settingStore.systemThemeType = actualTheme + } - // 设置主题 class - const currentTheme = AppConfig.systemThemeStyles[actualTheme as keyof SystemThemeTypes] - if (currentTheme) { - el.setAttribute('class', currentTheme.className) - } + // 设置主题 class + const currentTheme = AppConfig.systemThemeStyles[actualTheme as keyof SystemThemeTypes] + if (currentTheme) { + el.setAttribute('class', currentTheme.className) + } - // 设置主题颜色 - setElementThemeColor(settingStore.systemThemeColor) + // 设置主题颜色 + setElementThemeColor(settingStore.systemThemeColor) - // 设置圆角 - document.documentElement.style.setProperty('--custom-radius', `${settingStore.customRadius}rem`) - } + // 设置圆角 + document.documentElement.style.setProperty( + '--custom-radius', + `${settingStore.customRadius}rem` + ) + } - // 应用主题 - applyThemeByMode() + // 应用主题 + applyThemeByMode() - // 如果是 AUTO 模式,监听系统主题变化(使用 VueUse 的响应式特性) - if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { - watch( - prefersDark, - () => { - // 只有在 AUTO 模式下才响应系统主题变化 - if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { - applyThemeByMode() - } - }, - { immediate: false } - ) - } + // 如果是 AUTO 模式,监听系统主题变化(使用 VueUse 的响应式特性) + if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { + watch( + prefersDark, + () => { + // 只有在 AUTO 模式下才响应系统主题变化 + if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { + applyThemeByMode() + } + }, + { immediate: false } + ) + } } diff --git a/src/locales/index.ts b/src/locales/index.ts index 36c2648..9263f5a 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -40,8 +40,8 @@ const storageKeyManager = new StorageKeyManager() * 语言消息对象 */ const messages = { - [LanguageEnum.EN]: enMessages, - [LanguageEnum.ZH]: zhMessages + [LanguageEnum.EN]: enMessages, + [LanguageEnum.ZH]: zhMessages } /** @@ -49,8 +49,8 @@ const messages = { * 用于语言切换下拉框 */ export const languageOptions = [ - { value: LanguageEnum.ZH, label: '简体中文' }, - { value: LanguageEnum.EN, label: 'English' } + { value: LanguageEnum.ZH, label: '简体中文' }, + { value: LanguageEnum.EN, label: 'English' } ] /** @@ -58,48 +58,48 @@ export const languageOptions = [ * @returns 语言设置,如果获取失败则返回默认语言 */ const getDefaultLanguage = (): LanguageEnum => { - // 尝试从版本化的存储中获取语言设置 - try { - const storageKey = storageKeyManager.getStorageKey('user') - const userStore = localStorage.getItem(storageKey) + // 尝试从版本化的存储中获取语言设置 + try { + const storageKey = storageKeyManager.getStorageKey('user') + const userStore = localStorage.getItem(storageKey) - if (userStore) { - const { language } = JSON.parse(userStore) - if (language && Object.values(LanguageEnum).includes(language)) { - return language - } - } - } catch (error) { - console.warn('[i18n] 从版本化存储获取语言设置失败:', error) - } + if (userStore) { + const { language } = JSON.parse(userStore) + if (language && Object.values(LanguageEnum).includes(language)) { + return language + } + } + } catch (error) { + console.warn('[i18n] 从版本化存储获取语言设置失败:', error) + } - // 尝试从系统存储中获取语言设置 - try { - const sys = getSystemStorage() - if (sys) { - const { user } = JSON.parse(sys) - if (user?.language && Object.values(LanguageEnum).includes(user.language)) { - return user.language - } - } - } catch (error) { - console.warn('[i18n] 从系统存储获取语言设置失败:', error) - } + // 尝试从系统存储中获取语言设置 + try { + const sys = getSystemStorage() + if (sys) { + const { user } = JSON.parse(sys) + if (user?.language && Object.values(LanguageEnum).includes(user.language)) { + return user.language + } + } + } catch (error) { + console.warn('[i18n] 从系统存储获取语言设置失败:', error) + } - // 返回默认语言 - console.debug('[i18n] 使用默认语言:', LanguageEnum.ZH) - return LanguageEnum.ZH + // 返回默认语言 + console.debug('[i18n] 使用默认语言:', LanguageEnum.ZH) + return LanguageEnum.ZH } /** * i18n 配置选项 */ const i18nOptions: I18nOptions = { - locale: getDefaultLanguage(), - legacy: false, - globalInjection: true, - fallbackLocale: LanguageEnum.ZH, - messages + locale: getDefaultLanguage(), + legacy: false, + globalInjection: true, + fallbackLocale: LanguageEnum.ZH, + messages } /** @@ -111,7 +111,7 @@ const i18n: I18n = createI18n(i18nOptions) * 翻译函数类型 */ interface Translation { - (key: string): string + (key: string): string } /** diff --git a/src/locales/langs/en.json b/src/locales/langs/en.json index 51e196b..76c8225 100644 --- a/src/locales/langs/en.json +++ b/src/locales/langs/en.json @@ -1,296 +1,275 @@ { - "httpMsg": { - "unauthorized": "Unauthorized access, please login again", - "forbidden": "Access to this resource is forbidden", - "notFound": "The requested resource does not exist", - "methodNotAllowed": "Request method not allowed", - "requestTimeout": "Request timeout, please try again later", - "internalServerError": "Internal server error, please try again later", - "badGateway": "Bad gateway error, please try again later", - "serviceUnavailable": "Service temporarily unavailable, please try again later", - "gatewayTimeout": "Gateway timeout, please try again later", - "requestCancelled": "Request cancelled", - "networkError": "Network connection error, please check your connection", - "requestFailed": "Request failed", - "requestConfigError": "Request configuration error" - }, - "topBar": { - "search": { - "title": "Search" - }, - "user": { - "userCenter": "User center", - "docs": "Document", - "github": "Github", - "lockScreen": "Lock screen", - "logout": "Log out" - }, - "guide": { - "title": "Click here to view", - "theme": "Theme style", - "menu": "Open top menu", - "description": "More configurations" - } - }, - "common": { - "tips": "Prompt", - "cancel": "Cancel", - "confirm": "Confirm", - "logOutTips": "Do you want to log out?" - }, - "search": { - "placeholder": "Search page", - "historyTitle": "Search history", - "switchKeydown": "Navigate", - "selectKeydown": "Select", - "exitKeydown": "Close" - }, - "setting": { - "menuType": { - "title": "Menu Layout", - "list": [ - "Vertical", - "Horizontal", - "Mixed", - "Dual" - ] - }, - "theme": { - "title": "Theme Style", - "list": [ - "Light", - "Dark", - "System" - ] - }, - "menu": { - "title": "Menu Style" - }, - "color": { - "title": "Theme Color" - }, - "box": { - "title": "Box Style", - "list": [ - "Border", - "Shadow" - ] - }, - "container": { - "title": "Container Width", - "list": [ - "Full", - "Boxed" - ] - }, - "basics": { - "title": "Basic Config", - "list": { - "multiTab": "Show work tab", - "accordion": "Sidebar opens accordion", - "collapseSidebar": "Show sidebar button", - "reloadPage": "Show reload page button", - "fastEnter": "Show fast enter", - "breadcrumb": "Show crumb navigation", - "language": "Show multilingual selection", - "progressBar": "Show top progress bar", - "weakMode": "Color Weakness Mode", - "watermark": "Global watermark", - "menuWidth": "Menu width", - "tabStyle": "Tab style", - "pageTransition": "Page animation", - "borderRadius": "Custom radius" - } - }, - "tabStyle": { - "default": "Default", - "card": "Card", - "google": "Chrome" - }, - "transition": { - "list": { - "none": "None", - "fade": "Fade", - "slideLeft": "Slide Left", - "slideBottom": "Slide Bottom", - "slideTop": "Slide Top" - } - }, - "actions": { - "resetConfig": "Reset Config", - "copyConfig": "Copy Config", - "copySuccess": "Configuration copied to clipboard, paste it into src/config/setting.ts file", - "copyFailed": "Copy failed, please try again", - "resetFailed": "Reset failed, please refresh the page and try again" - } - }, - "notice": { - "title": "Notice", - "btnRead": "Mark as read", - "bar": [ - "Notice", - "Message", - "Todo" - ], - "text": [ - "No" - ], - "viewAll": "View all" - }, - "worktab": { - "btn": { - "refresh": "Refresh", - "fixed": "Fixed", - "unfixed": "Unfixed", - "closeLeft": "Close left", - "closeRight": "Close right", - "closeOther": "Close other", - "closeAll": "Close all" - } - }, - "login": { - "leftView": { - "title": "A backend system of beauty and efficiency", - "subTitle": "A sleek and practical interface for a great user experience" - }, - "title": "Welcome back", - "subTitle": "Please enter your account and password to login", - "roles": { - "super": "Super Admin", - "admin": "Admin", - "user": "User" - }, - "placeholder": { - "username": "Please enter your account", - "password": "Please enter your password", - "slider": "Please slide to verify" - }, - "sliderText": "Please slide to verify", - "sliderSuccessText": "Verification successful", - "rememberPwd": "Remember password", - "forgetPwd": "Forgot password", - "btnText": "Login", - "noAccount": "No account yet?", - "register": "Register", - "success": { - "title": "Login successful", - "message": "Welcome back" - } - }, - "forgetPassword": { - "title": "Forgot password?", - "subTitle": "Enter your email to reset your password", - "placeholder": "Please enter your email", - "submitBtnText": "Submit", - "backBtnText": "Back" - }, - "register": { - "title": "Create account", - "subTitle": "Welcome to join us, please fill in the following information to complete the registration", - "placeholder": { - "username": "Please enter your account", - "password": "Please enter your password", - "confirmPassword": "Please enter your password again" - }, - "rule": { - "confirmPasswordRequired": "Please enter your password again", - "passwordMismatch": "The two passwords are inconsistent!", - "usernameLength": "The length is 3 to 20 characters", - "passwordLength": "The password length cannot be less than 6 digits", - "agreementRequired": "Please agree to the privacy policy" - }, - "agreeText": "I agree", - "privacyPolicy": "Privacy policy", - "submitBtnText": "Register", - "hasAccount": "Already have an account?", - "toLogin": "To login" - }, - "lockScreen": { - "pwdError": "Password error", - "lock": { - "inputPlaceholder": "Please input lock screen password", - "btnText": "Lock" - }, - "unlock": { - "inputPlaceholder": "Please input unlock password", - "btnText": "Unlock", - "backBtnText": "Back to login" - } - }, - "greeting": { - "dawn": "Good morning!", - "morning": "Good morning!", - "afternoon": "Good afternoon!", - "evening": "Good evening!" - }, - "exceptionPage": { - "403": "Sorry, you do not have permission to access this page", - "404": "Sorry, the page you are trying to access does not exist", - "500": "Sorry, there was an error on the server", - "gohome": "Go Home" - }, - "menus": { - "login": { - "title": "Login" - }, - "register": { - "title": "Register" - }, - "forgetPassword": { - "title": "Forget Password" - }, - "outside": { - "title": "Outside" - }, - "dashboard": { - "title": "Dashboard", - "console": "Console" - }, - "result": { - "title": "Result Page", - "success": "Success", - "fail": "Fail" - }, - "exception": { - "title": "Exception", - "forbidden": "403", - "notFound": "404", - "serverError": "500" - }, - "system": { - "title": "System Settings", - "user": "User Manage", - "role": "Role Manage", - "userCenter": "User Center", - "menu": "Menu Manage" - } - }, - "table": { - "form": { - "reset": "Reset", - "submit": "Submit" - }, - "searchBar": { - "reset": "Reset", - "search": "Search", - "expand": "Expand", - "collapse": "Collapse", - "searchInputPlaceholder": "Please enter", - "searchSelectPlaceholder": "Please select" - }, - "selection": "Select", - "sizeOptions": { - "small": "Compact", - "default": "Default", - "large": "Loose" - }, - "column": { - "selection": "Select", - "expand": "Expand", - "index": "Index" - }, - "zebra": "Zebra", - "border": "Border", - "headerBackground": "Header BG" - } -} \ No newline at end of file + "httpMsg": { + "unauthorized": "Unauthorized access, please login again", + "forbidden": "Access to this resource is forbidden", + "notFound": "The requested resource does not exist", + "methodNotAllowed": "Request method not allowed", + "requestTimeout": "Request timeout, please try again later", + "internalServerError": "Internal server error, please try again later", + "badGateway": "Bad gateway error, please try again later", + "serviceUnavailable": "Service temporarily unavailable, please try again later", + "gatewayTimeout": "Gateway timeout, please try again later", + "requestCancelled": "Request cancelled", + "networkError": "Network connection error, please check your connection", + "requestFailed": "Request failed", + "requestConfigError": "Request configuration error" + }, + "topBar": { + "search": { + "title": "Search" + }, + "user": { + "userCenter": "User center", + "docs": "Document", + "github": "Github", + "lockScreen": "Lock screen", + "logout": "Log out" + }, + "guide": { + "title": "Click here to view", + "theme": "Theme style", + "menu": "Open top menu", + "description": "More configurations" + } + }, + "common": { + "tips": "Prompt", + "cancel": "Cancel", + "confirm": "Confirm", + "logOutTips": "Do you want to log out?" + }, + "search": { + "placeholder": "Search page", + "historyTitle": "Search history", + "switchKeydown": "Navigate", + "selectKeydown": "Select", + "exitKeydown": "Close" + }, + "setting": { + "menuType": { + "title": "Menu Layout", + "list": ["Vertical", "Horizontal", "Mixed", "Dual"] + }, + "theme": { + "title": "Theme Style", + "list": ["Light", "Dark", "System"] + }, + "menu": { + "title": "Menu Style" + }, + "color": { + "title": "Theme Color" + }, + "box": { + "title": "Box Style", + "list": ["Border", "Shadow"] + }, + "container": { + "title": "Container Width", + "list": ["Full", "Boxed"] + }, + "basics": { + "title": "Basic Config", + "list": { + "multiTab": "Show work tab", + "accordion": "Sidebar opens accordion", + "collapseSidebar": "Show sidebar button", + "reloadPage": "Show reload page button", + "fastEnter": "Show fast enter", + "breadcrumb": "Show crumb navigation", + "language": "Show multilingual selection", + "progressBar": "Show top progress bar", + "weakMode": "Color Weakness Mode", + "watermark": "Global watermark", + "menuWidth": "Menu width", + "tabStyle": "Tab style", + "pageTransition": "Page animation", + "borderRadius": "Custom radius" + } + }, + "tabStyle": { + "default": "Default", + "card": "Card", + "google": "Chrome" + }, + "transition": { + "list": { + "none": "None", + "fade": "Fade", + "slideLeft": "Slide Left", + "slideBottom": "Slide Bottom", + "slideTop": "Slide Top" + } + }, + "actions": { + "resetConfig": "Reset Config", + "copyConfig": "Copy Config", + "copySuccess": "Configuration copied to clipboard, paste it into src/config/setting.ts file", + "copyFailed": "Copy failed, please try again", + "resetFailed": "Reset failed, please refresh the page and try again" + } + }, + "notice": { + "title": "Notice", + "btnRead": "Mark as read", + "bar": ["Notice", "Message", "Todo"], + "text": ["No"], + "viewAll": "View all" + }, + "worktab": { + "btn": { + "refresh": "Refresh", + "fixed": "Fixed", + "unfixed": "Unfixed", + "closeLeft": "Close left", + "closeRight": "Close right", + "closeOther": "Close other", + "closeAll": "Close all" + } + }, + "login": { + "leftView": { + "title": "A backend system of beauty and efficiency", + "subTitle": "A sleek and practical interface for a great user experience" + }, + "title": "Welcome back", + "subTitle": "Please enter your account and password to login", + "roles": { + "super": "Super Admin", + "admin": "Admin", + "user": "User" + }, + "placeholder": { + "username": "Please enter your account", + "password": "Please enter your password", + "slider": "Please slide to verify" + }, + "sliderText": "Please slide to verify", + "sliderSuccessText": "Verification successful", + "rememberPwd": "Remember password", + "forgetPwd": "Forgot password", + "btnText": "Login", + "noAccount": "No account yet?", + "register": "Register", + "success": { + "title": "Login successful", + "message": "Welcome back" + } + }, + "forgetPassword": { + "title": "Forgot password?", + "subTitle": "Enter your email to reset your password", + "placeholder": "Please enter your email", + "submitBtnText": "Submit", + "backBtnText": "Back" + }, + "register": { + "title": "Create account", + "subTitle": "Welcome to join us, please fill in the following information to complete the registration", + "placeholder": { + "username": "Please enter your account", + "password": "Please enter your password", + "confirmPassword": "Please enter your password again" + }, + "rule": { + "confirmPasswordRequired": "Please enter your password again", + "passwordMismatch": "The two passwords are inconsistent!", + "usernameLength": "The length is 3 to 20 characters", + "passwordLength": "The password length cannot be less than 6 digits", + "agreementRequired": "Please agree to the privacy policy" + }, + "agreeText": "I agree", + "privacyPolicy": "Privacy policy", + "submitBtnText": "Register", + "hasAccount": "Already have an account?", + "toLogin": "To login" + }, + "lockScreen": { + "pwdError": "Password error", + "lock": { + "inputPlaceholder": "Please input lock screen password", + "btnText": "Lock" + }, + "unlock": { + "inputPlaceholder": "Please input unlock password", + "btnText": "Unlock", + "backBtnText": "Back to login" + } + }, + "greeting": { + "dawn": "Good morning!", + "morning": "Good morning!", + "afternoon": "Good afternoon!", + "evening": "Good evening!" + }, + "exceptionPage": { + "403": "Sorry, you do not have permission to access this page", + "404": "Sorry, the page you are trying to access does not exist", + "500": "Sorry, there was an error on the server", + "gohome": "Go Home" + }, + "menus": { + "login": { + "title": "Login" + }, + "register": { + "title": "Register" + }, + "forgetPassword": { + "title": "Forget Password" + }, + "outside": { + "title": "Outside" + }, + "dashboard": { + "title": "Dashboard", + "console": "Console" + }, + "result": { + "title": "Result Page", + "success": "Success", + "fail": "Fail" + }, + "exception": { + "title": "Exception", + "forbidden": "403", + "notFound": "404", + "serverError": "500" + }, + "system": { + "title": "System Settings", + "user": "User Manage", + "role": "Role Manage", + "userCenter": "User Center", + "menu": "Menu Manage" + } + }, + "table": { + "form": { + "reset": "Reset", + "submit": "Submit" + }, + "searchBar": { + "reset": "Reset", + "search": "Search", + "expand": "Expand", + "collapse": "Collapse", + "searchInputPlaceholder": "Please enter", + "searchSelectPlaceholder": "Please select" + }, + "selection": "Select", + "sizeOptions": { + "small": "Compact", + "default": "Default", + "large": "Loose" + }, + "column": { + "selection": "Select", + "expand": "Expand", + "index": "Index" + }, + "zebra": "Zebra", + "border": "Border", + "headerBackground": "Header BG" + } +} diff --git a/src/locales/langs/zh.json b/src/locales/langs/zh.json index 77a23a5..879c363 100644 --- a/src/locales/langs/zh.json +++ b/src/locales/langs/zh.json @@ -1,296 +1,275 @@ { - "httpMsg": { - "unauthorized": "未授权访问,请重新登录", - "forbidden": "禁止访问该资源", - "notFound": "请求的资源不存在", - "methodNotAllowed": "请求方法不允许", - "requestTimeout": "请求超时,请稍后重试", - "internalServerError": "服务器内部错误,请稍后重试", - "badGateway": "网关错误,请稍后重试", - "serviceUnavailable": "服务暂时不可用,请稍后重试", - "gatewayTimeout": "网关超时,请稍后重试", - "requestCancelled": "请求已取消", - "networkError": "网络连接异常,请检查网络连接", - "requestFailed": "请求失败", - "requestConfigError": "请求配置错误" - }, - "topBar": { - "search": { - "title": "搜索" - }, - "user": { - "userCenter": "个人中心", - "docs": "使用文档", - "github": "Github", - "lockScreen": "锁定屏幕", - "logout": "退出登录" - }, - "guide": { - "title": "点击这里查看", - "theme": "主题风格", - "menu": "开启顶栏菜单", - "description": "等更多配置" - } - }, - "common": { - "tips": "提示", - "cancel": "取消", - "confirm": "确定", - "logOutTips": "您是否要退出登录?" - }, - "search": { - "placeholder": "搜索页面", - "historyTitle": "搜索历史", - "switchKeydown": "切换", - "selectKeydown": "选择", - "exitKeydown": "关闭" - }, - "setting": { - "menuType": { - "title": "菜单布局", - "list": [ - "垂直", - "水平", - "混合", - "双列" - ] - }, - "theme": { - "title": "主题风格", - "list": [ - "浅色", - "深色", - "系统" - ] - }, - "menu": { - "title": "菜单风格" - }, - "color": { - "title": "系统主题色" - }, - "box": { - "title": "盒子样式", - "list": [ - "边框", - "阴影" - ] - }, - "container": { - "title": "容器宽度", - "list": [ - "铺满", - "定宽" - ] - }, - "basics": { - "title": "基础配置", - "list": { - "multiTab": "开启多标签栏", - "accordion": "侧边栏开启手风琴模式", - "collapseSidebar": "显示折叠侧边栏按钮", - "fastEnter": "显示快速入口", - "reloadPage": "显示重载页面按钮", - "breadcrumb": "显示全局面包屑导航", - "language": "显示多语言选择", - "progressBar": "显示顶部进度条", - "weakMode": "色弱模式", - "watermark": "全局水印", - "menuWidth": "菜单宽度", - "tabStyle": "标签页风格", - "pageTransition": "页面切换动画", - "borderRadius": "自定义圆角" - } - }, - "tabStyle": { - "default": "默认", - "card": "卡片", - "google": "谷歌" - }, - "transition": { - "list": { - "none": "无动画", - "fade": "淡入淡出", - "slideLeft": "左侧滑入", - "slideBottom": "下方滑入", - "slideTop": "上方滑入" - } - }, - "actions": { - "resetConfig": "重置配置", - "copyConfig": "复制配置", - "copySuccess": "配置已复制到剪贴板,可粘贴到 src/config/setting.ts 文件中", - "copyFailed": "复制失败,请重试", - "resetFailed": "重置失败,请刷新页面后重试" - } - }, - "notice": { - "title": "通知", - "btnRead": "标为已读", - "bar": [ - "通知", - "消息", - "代办" - ], - "text": [ - "暂无" - ], - "viewAll": "查看全部" - }, - "worktab": { - "btn": { - "refresh": "刷新", - "fixed": "固定", - "unfixed": "取消固定", - "closeLeft": "关闭左侧", - "closeRight": "关闭右侧", - "closeOther": "关闭其他", - "closeAll": "关闭全部" - } - }, - "login": { - "leftView": { - "title": "一款兼具设计美学与高效开发的后台系统", - "subTitle": "美观实用的界面,经过视觉优化,确保卓越的用户体验" - }, - "title": "欢迎回来", - "subTitle": "输入您的账号和密码登录", - "roles": { - "super": "超级管理员", - "admin": "管理员", - "user": "普通用户" - }, - "placeholder": { - "username": "请输入账号", - "password": "请输入密码", - "slider": "请拖动滑块完成验证" - }, - "sliderText": "按住滑块拖动", - "sliderSuccessText": "验证成功", - "rememberPwd": "记住密码", - "forgetPwd": "忘记密码", - "btnText": "登录", - "noAccount": "还没有账号?", - "register": "注册", - "success": { - "title": "登录成功", - "message": "欢迎回来" - } - }, - "forgetPassword": { - "title": "忘记密码?", - "subTitle": "输入您的电子邮件来重置您的密码", - "placeholder": "请输入您的电子邮件", - "submitBtnText": "提交", - "backBtnText": "返回" - }, - "register": { - "title": "创建账号", - "subTitle": "欢迎加入我们,请填写以下信息完成注册", - "placeholder": { - "username": "请输入账号", - "password": "请输入密码", - "confirmPassword": "请再次输入密码" - }, - "rule": { - "confirmPasswordRequired": "请再次输入密码", - "passwordMismatch": "两次输入密码不一致!", - "usernameLength": "长度在 3 到 20 个字符", - "passwordLength": "密码长度不能小于6位", - "agreementRequired": "请同意隐私协议" - }, - "agreeText": "我同意", - "privacyPolicy": "《隐私政策》", - "submitBtnText": "注册", - "hasAccount": "已有账号?", - "toLogin": "去登录" - }, - "lockScreen": { - "pwdError": "密码错误", - "lock": { - "inputPlaceholder": "请输入锁屏密码", - "btnText": "锁定" - }, - "unlock": { - "inputPlaceholder": "请输入解锁密码", - "btnText": "解锁", - "backBtnText": "返回登录" - } - }, - "greeting": { - "dawn": "凌晨了!", - "morning": "上午好!", - "afternoon": "下午好!", - "evening": "晚上好!" - }, - "exceptionPage": { - "403": "抱歉,您无权访问该页面", - "404": "抱歉,您访问的页面不存在", - "500": "抱歉,服务器出错了", - "gohome": "返回首页" - }, - "menus": { - "login": { - "title": "登录" - }, - "register": { - "title": "注册" - }, - "forgetPassword": { - "title": "忘记密码" - }, - "outside": { - "title": "内嵌页面" - }, - "dashboard": { - "title": "仪表盘", - "console": "工作台" - }, - "result": { - "title": "结果页面", - "success": "成功页", - "fail": "失败页" - }, - "exception": { - "title": "异常页面", - "forbidden": "403", - "notFound": "404", - "serverError": "500" - }, - "system": { - "title": "系统管理", - "user": "用户管理", - "role": "角色管理", - "userCenter": "个人中心", - "menu": "菜单管理" - } - }, - "table": { - "form": { - "reset": "重置", - "submit": "提交" - }, - "searchBar": { - "reset": "重置", - "search": "查询", - "expand": "展开", - "collapse": "收起", - "searchInputPlaceholder": "请输入", - "searchSelectPlaceholder": "请选择" - }, - "selection": "选择", - "sizeOptions": { - "small": "紧凑", - "default": "默认", - "large": "宽松" - }, - "column": { - "selection": "勾选", - "expand": "展开", - "index": "序号" - }, - "zebra": "斑马纹", - "border": "边框", - "headerBackground": "表头背景" - } -} \ No newline at end of file + "httpMsg": { + "unauthorized": "未授权访问,请重新登录", + "forbidden": "禁止访问该资源", + "notFound": "请求的资源不存在", + "methodNotAllowed": "请求方法不允许", + "requestTimeout": "请求超时,请稍后重试", + "internalServerError": "服务器内部错误,请稍后重试", + "badGateway": "网关错误,请稍后重试", + "serviceUnavailable": "服务暂时不可用,请稍后重试", + "gatewayTimeout": "网关超时,请稍后重试", + "requestCancelled": "请求已取消", + "networkError": "网络连接异常,请检查网络连接", + "requestFailed": "请求失败", + "requestConfigError": "请求配置错误" + }, + "topBar": { + "search": { + "title": "搜索" + }, + "user": { + "userCenter": "个人中心", + "docs": "使用文档", + "github": "Github", + "lockScreen": "锁定屏幕", + "logout": "退出登录" + }, + "guide": { + "title": "点击这里查看", + "theme": "主题风格", + "menu": "开启顶栏菜单", + "description": "等更多配置" + } + }, + "common": { + "tips": "提示", + "cancel": "取消", + "confirm": "确定", + "logOutTips": "您是否要退出登录?" + }, + "search": { + "placeholder": "搜索页面", + "historyTitle": "搜索历史", + "switchKeydown": "切换", + "selectKeydown": "选择", + "exitKeydown": "关闭" + }, + "setting": { + "menuType": { + "title": "菜单布局", + "list": ["垂直", "水平", "混合", "双列"] + }, + "theme": { + "title": "主题风格", + "list": ["浅色", "深色", "系统"] + }, + "menu": { + "title": "菜单风格" + }, + "color": { + "title": "系统主题色" + }, + "box": { + "title": "盒子样式", + "list": ["边框", "阴影"] + }, + "container": { + "title": "容器宽度", + "list": ["铺满", "定宽"] + }, + "basics": { + "title": "基础配置", + "list": { + "multiTab": "开启多标签栏", + "accordion": "侧边栏开启手风琴模式", + "collapseSidebar": "显示折叠侧边栏按钮", + "fastEnter": "显示快速入口", + "reloadPage": "显示重载页面按钮", + "breadcrumb": "显示全局面包屑导航", + "language": "显示多语言选择", + "progressBar": "显示顶部进度条", + "weakMode": "色弱模式", + "watermark": "全局水印", + "menuWidth": "菜单宽度", + "tabStyle": "标签页风格", + "pageTransition": "页面切换动画", + "borderRadius": "自定义圆角" + } + }, + "tabStyle": { + "default": "默认", + "card": "卡片", + "google": "谷歌" + }, + "transition": { + "list": { + "none": "无动画", + "fade": "淡入淡出", + "slideLeft": "左侧滑入", + "slideBottom": "下方滑入", + "slideTop": "上方滑入" + } + }, + "actions": { + "resetConfig": "重置配置", + "copyConfig": "复制配置", + "copySuccess": "配置已复制到剪贴板,可粘贴到 src/config/setting.ts 文件中", + "copyFailed": "复制失败,请重试", + "resetFailed": "重置失败,请刷新页面后重试" + } + }, + "notice": { + "title": "通知", + "btnRead": "标为已读", + "bar": ["通知", "消息", "代办"], + "text": ["暂无"], + "viewAll": "查看全部" + }, + "worktab": { + "btn": { + "refresh": "刷新", + "fixed": "固定", + "unfixed": "取消固定", + "closeLeft": "关闭左侧", + "closeRight": "关闭右侧", + "closeOther": "关闭其他", + "closeAll": "关闭全部" + } + }, + "login": { + "leftView": { + "title": "一款兼具设计美学与高效开发的后台系统", + "subTitle": "美观实用的界面,经过视觉优化,确保卓越的用户体验" + }, + "title": "欢迎回来", + "subTitle": "输入您的账号和密码登录", + "roles": { + "super": "超级管理员", + "admin": "管理员", + "user": "普通用户" + }, + "placeholder": { + "username": "请输入账号", + "password": "请输入密码", + "slider": "请拖动滑块完成验证" + }, + "sliderText": "按住滑块拖动", + "sliderSuccessText": "验证成功", + "rememberPwd": "记住密码", + "forgetPwd": "忘记密码", + "btnText": "登录", + "noAccount": "还没有账号?", + "register": "注册", + "success": { + "title": "登录成功", + "message": "欢迎回来" + } + }, + "forgetPassword": { + "title": "忘记密码?", + "subTitle": "输入您的电子邮件来重置您的密码", + "placeholder": "请输入您的电子邮件", + "submitBtnText": "提交", + "backBtnText": "返回" + }, + "register": { + "title": "创建账号", + "subTitle": "欢迎加入我们,请填写以下信息完成注册", + "placeholder": { + "username": "请输入账号", + "password": "请输入密码", + "confirmPassword": "请再次输入密码" + }, + "rule": { + "confirmPasswordRequired": "请再次输入密码", + "passwordMismatch": "两次输入密码不一致!", + "usernameLength": "长度在 3 到 20 个字符", + "passwordLength": "密码长度不能小于6位", + "agreementRequired": "请同意隐私协议" + }, + "agreeText": "我同意", + "privacyPolicy": "《隐私政策》", + "submitBtnText": "注册", + "hasAccount": "已有账号?", + "toLogin": "去登录" + }, + "lockScreen": { + "pwdError": "密码错误", + "lock": { + "inputPlaceholder": "请输入锁屏密码", + "btnText": "锁定" + }, + "unlock": { + "inputPlaceholder": "请输入解锁密码", + "btnText": "解锁", + "backBtnText": "返回登录" + } + }, + "greeting": { + "dawn": "凌晨了!", + "morning": "上午好!", + "afternoon": "下午好!", + "evening": "晚上好!" + }, + "exceptionPage": { + "403": "抱歉,您无权访问该页面", + "404": "抱歉,您访问的页面不存在", + "500": "抱歉,服务器出错了", + "gohome": "返回首页" + }, + "menus": { + "login": { + "title": "登录" + }, + "register": { + "title": "注册" + }, + "forgetPassword": { + "title": "忘记密码" + }, + "outside": { + "title": "内嵌页面" + }, + "dashboard": { + "title": "仪表盘", + "console": "工作台" + }, + "result": { + "title": "结果页面", + "success": "成功页", + "fail": "失败页" + }, + "exception": { + "title": "异常页面", + "forbidden": "403", + "notFound": "404", + "serverError": "500" + }, + "system": { + "title": "系统管理", + "user": "用户管理", + "role": "角色管理", + "userCenter": "个人中心", + "menu": "菜单管理" + } + }, + "table": { + "form": { + "reset": "重置", + "submit": "提交" + }, + "searchBar": { + "reset": "重置", + "search": "查询", + "expand": "展开", + "collapse": "收起", + "searchInputPlaceholder": "请输入", + "searchSelectPlaceholder": "请选择" + }, + "selection": "选择", + "sizeOptions": { + "small": "紧凑", + "default": "默认", + "large": "宽松" + }, + "column": { + "selection": "勾选", + "expand": "展开", + "index": "序号" + }, + "zebra": "斑马纹", + "border": "边框", + "headerBackground": "表头背景" + } +} diff --git a/src/mock/temp/formData.ts b/src/mock/temp/formData.ts index 8aa7ae5..2c06829 100644 --- a/src/mock/temp/formData.ts +++ b/src/mock/temp/formData.ts @@ -10,264 +10,264 @@ import avatar9 from '@/assets/images/avatar/avatar9.webp' import avatar10 from '@/assets/images/avatar/avatar10.webp' export interface User { - id: number - username: string - gender: 1 | 0 - mobile: string - email: string - dep: string - status: string - create_time: string - avatar: string + id: number + username: string + gender: 1 | 0 + mobile: string + email: string + dep: string + status: string + create_time: string + avatar: string } // 用户列表 export const ACCOUNT_TABLE_DATA: User[] = [ - { - id: 1, - username: 'alexmorgan', - gender: 1, - mobile: '18670001591', - email: 'alexmorgan@company.com', - dep: '研发部', - status: '1', - create_time: '2020-09-09 10:01:10', - avatar: avatar1 - }, - { - id: 2, - username: 'sophiabaker', - gender: 1, - mobile: '17766664444', - email: 'sophiabaker@company.com', - dep: '电商部', - status: '1', - create_time: '2020-10-10 13:01:12', - avatar: avatar2 - }, - { - id: 3, - username: 'liampark', - gender: 1, - mobile: '18670001597', - email: 'liampark@company.com', - dep: '人事部', - status: '1', - create_time: '2020-11-14 12:01:45', - avatar: avatar3 - }, - { - id: 4, - username: 'oliviagrant', - gender: 0, - mobile: '18670001596', - email: 'oliviagrant@company.com', - dep: '产品部', - status: '1', - create_time: '2020-11-14 09:01:20', - avatar: avatar4 - }, - { - id: 5, - username: 'emmawilson', - gender: 0, - mobile: '18670001595', - email: 'emmawilson@company.com', - dep: '财务部', - status: '1', - create_time: '2020-11-13 11:01:05', - avatar: avatar5 - }, - { - id: 6, - username: 'noahevan', - gender: 1, - mobile: '18670001594', - email: 'noahevan@company.com', - dep: '运营部', - status: '1', - create_time: '2020-10-11 13:10:26', - avatar: avatar6 - }, - { - id: 7, - username: 'avamartin', - gender: 1, - mobile: '18123820191', - email: 'avamartin@company.com', - dep: '客服部', - status: '2', - create_time: '2020-05-14 12:05:10', - avatar: avatar7 - }, - { - id: 8, - username: 'jacoblee', - gender: 1, - mobile: '18670001592', - email: 'jacoblee@company.com', - dep: '总经办', - status: '3', - create_time: '2020-11-12 07:22:25', - avatar: avatar8 - }, - { - id: 9, - username: 'miaclark', - gender: 0, - mobile: '18670001581', - email: 'miaclark@company.com', - dep: '研发部', - status: '4', - create_time: '2020-06-12 05:04:20', - avatar: avatar9 - }, - { - id: 10, - username: 'ethanharris', - gender: 1, - mobile: '13755554444', - email: 'ethanharris@company.com', - dep: '研发部', - status: '1', - create_time: '2020-11-12 16:01:10', - avatar: avatar10 - }, - { - id: 11, - username: 'isabellamoore', - gender: 1, - mobile: '13766660000', - email: 'isabellamoore@company.com', - dep: '研发部', - status: '1', - create_time: '2020-11-14 12:01:20', - avatar: avatar6 - }, - { - id: 12, - username: 'masonwhite', - gender: 1, - mobile: '18670001502', - email: 'masonwhite@company.com', - dep: '研发部', - status: '1', - create_time: '2020-11-14 12:01:20', - avatar: avatar7 - }, - { - id: 13, - username: 'charlottehall', - gender: 1, - mobile: '13006644977', - email: 'charlottehall@company.com', - dep: '研发部', - status: '1', - create_time: '2020-11-14 12:01:20', - avatar: avatar8 - }, - { - id: 14, - username: 'benjaminscott', - gender: 0, - mobile: '13599998888', - email: 'benjaminscott@company.com', - dep: '研发部', - status: '1', - create_time: '2020-11-14 12:01:20', - avatar: avatar9 - }, - { - id: 15, - username: 'ameliaking', - gender: 1, - mobile: '13799998888', - email: 'ameliaking@company.com', - dep: '研发部', - status: '1', - create_time: '2020-11-14 12:01:20', - avatar: avatar10 - } + { + id: 1, + username: 'alexmorgan', + gender: 1, + mobile: '18670001591', + email: 'alexmorgan@company.com', + dep: '研发部', + status: '1', + create_time: '2020-09-09 10:01:10', + avatar: avatar1 + }, + { + id: 2, + username: 'sophiabaker', + gender: 1, + mobile: '17766664444', + email: 'sophiabaker@company.com', + dep: '电商部', + status: '1', + create_time: '2020-10-10 13:01:12', + avatar: avatar2 + }, + { + id: 3, + username: 'liampark', + gender: 1, + mobile: '18670001597', + email: 'liampark@company.com', + dep: '人事部', + status: '1', + create_time: '2020-11-14 12:01:45', + avatar: avatar3 + }, + { + id: 4, + username: 'oliviagrant', + gender: 0, + mobile: '18670001596', + email: 'oliviagrant@company.com', + dep: '产品部', + status: '1', + create_time: '2020-11-14 09:01:20', + avatar: avatar4 + }, + { + id: 5, + username: 'emmawilson', + gender: 0, + mobile: '18670001595', + email: 'emmawilson@company.com', + dep: '财务部', + status: '1', + create_time: '2020-11-13 11:01:05', + avatar: avatar5 + }, + { + id: 6, + username: 'noahevan', + gender: 1, + mobile: '18670001594', + email: 'noahevan@company.com', + dep: '运营部', + status: '1', + create_time: '2020-10-11 13:10:26', + avatar: avatar6 + }, + { + id: 7, + username: 'avamartin', + gender: 1, + mobile: '18123820191', + email: 'avamartin@company.com', + dep: '客服部', + status: '2', + create_time: '2020-05-14 12:05:10', + avatar: avatar7 + }, + { + id: 8, + username: 'jacoblee', + gender: 1, + mobile: '18670001592', + email: 'jacoblee@company.com', + dep: '总经办', + status: '3', + create_time: '2020-11-12 07:22:25', + avatar: avatar8 + }, + { + id: 9, + username: 'miaclark', + gender: 0, + mobile: '18670001581', + email: 'miaclark@company.com', + dep: '研发部', + status: '4', + create_time: '2020-06-12 05:04:20', + avatar: avatar9 + }, + { + id: 10, + username: 'ethanharris', + gender: 1, + mobile: '13755554444', + email: 'ethanharris@company.com', + dep: '研发部', + status: '1', + create_time: '2020-11-12 16:01:10', + avatar: avatar10 + }, + { + id: 11, + username: 'isabellamoore', + gender: 1, + mobile: '13766660000', + email: 'isabellamoore@company.com', + dep: '研发部', + status: '1', + create_time: '2020-11-14 12:01:20', + avatar: avatar6 + }, + { + id: 12, + username: 'masonwhite', + gender: 1, + mobile: '18670001502', + email: 'masonwhite@company.com', + dep: '研发部', + status: '1', + create_time: '2020-11-14 12:01:20', + avatar: avatar7 + }, + { + id: 13, + username: 'charlottehall', + gender: 1, + mobile: '13006644977', + email: 'charlottehall@company.com', + dep: '研发部', + status: '1', + create_time: '2020-11-14 12:01:20', + avatar: avatar8 + }, + { + id: 14, + username: 'benjaminscott', + gender: 0, + mobile: '13599998888', + email: 'benjaminscott@company.com', + dep: '研发部', + status: '1', + create_time: '2020-11-14 12:01:20', + avatar: avatar9 + }, + { + id: 15, + username: 'ameliaking', + gender: 1, + mobile: '13799998888', + email: 'ameliaking@company.com', + dep: '研发部', + status: '1', + create_time: '2020-11-14 12:01:20', + avatar: avatar10 + } ] export interface Role { - roleName: string - roleCode: string - des: string - date: string - enable: boolean + roleName: string + roleCode: string + des: string + date: string + enable: boolean } // 角色列表 export const ROLE_LIST_DATA: Role[] = [ - { - roleName: '超级管理员', - roleCode: 'R_SUPER', - des: '拥有系统全部权限', - date: '2025-05-15 12:30:45', - enable: true - }, - { - roleName: '管理员', - roleCode: 'R_ADMIN', - des: '拥有系统管理权限', - date: '2025-05-15 12:30:45', - enable: true - }, - { - roleName: '普通用户', - roleCode: 'R_USER', - des: '拥有系统普通权限', - date: '2025-05-15 12:30:45', - enable: true - }, - { - roleName: '财务管理员', - roleCode: 'R_FINANCE', - des: '管理财务相关权限', - date: '2025-05-16 09:15:30', - enable: true - }, - { - roleName: '数据分析师', - roleCode: 'R_ANALYST', - des: '拥有数据分析权限', - date: '2025-05-16 11:45:00', - enable: false - }, - { - roleName: '客服专员', - roleCode: 'R_SUPPORT', - des: '处理客户支持请求', - date: '2025-05-17 14:30:22', - enable: true - }, - { - roleName: '营销经理', - roleCode: 'R_MARKETING', - des: '管理营销活动权限', - date: '2025-05-17 15:10:50', - enable: true - }, - { - roleName: '访客用户', - roleCode: 'R_GUEST', - des: '仅限浏览权限', - date: '2025-05-18 08:25:40', - enable: false - }, - { - roleName: '系统维护员', - roleCode: 'R_MAINTAINER', - des: '负责系统维护和更新', - date: '2025-05-18 09:50:12', - enable: true - }, - { - roleName: '项目经理', - roleCode: 'R_PM', - des: '管理项目相关权限', - date: '2025-05-19 13:40:35', - enable: true - } + { + roleName: '超级管理员', + roleCode: 'R_SUPER', + des: '拥有系统全部权限', + date: '2025-05-15 12:30:45', + enable: true + }, + { + roleName: '管理员', + roleCode: 'R_ADMIN', + des: '拥有系统管理权限', + date: '2025-05-15 12:30:45', + enable: true + }, + { + roleName: '普通用户', + roleCode: 'R_USER', + des: '拥有系统普通权限', + date: '2025-05-15 12:30:45', + enable: true + }, + { + roleName: '财务管理员', + roleCode: 'R_FINANCE', + des: '管理财务相关权限', + date: '2025-05-16 09:15:30', + enable: true + }, + { + roleName: '数据分析师', + roleCode: 'R_ANALYST', + des: '拥有数据分析权限', + date: '2025-05-16 11:45:00', + enable: false + }, + { + roleName: '客服专员', + roleCode: 'R_SUPPORT', + des: '处理客户支持请求', + date: '2025-05-17 14:30:22', + enable: true + }, + { + roleName: '营销经理', + roleCode: 'R_MARKETING', + des: '管理营销活动权限', + date: '2025-05-17 15:10:50', + enable: true + }, + { + roleName: '访客用户', + roleCode: 'R_GUEST', + des: '仅限浏览权限', + date: '2025-05-18 08:25:40', + enable: false + }, + { + roleName: '系统维护员', + roleCode: 'R_MAINTAINER', + des: '负责系统维护和更新', + date: '2025-05-18 09:50:12', + enable: true + }, + { + roleName: '项目经理', + roleCode: 'R_PM', + des: '管理项目相关权限', + date: '2025-05-19 13:40:35', + enable: true + } ] diff --git a/src/mock/upgrade/changeLog.ts b/src/mock/upgrade/changeLog.ts index dd6b772..97e91f0 100644 --- a/src/mock/upgrade/changeLog.ts +++ b/src/mock/upgrade/changeLog.ts @@ -1,12 +1,12 @@ import { ref } from 'vue' interface UpgradeLog { - version: string // 版本号 - title: string // 更新标题 - date: string // 更新日期 - detail?: string[] // 更新内容 - requireReLogin?: boolean // 是否需要重新登录 - remark?: string // 备注 + version: string // 版本号 + title: string // 更新标题 + date: string // 更新日期 + detail?: string[] // 更新内容 + requireReLogin?: boolean // 是否需要重新登录 + remark?: string // 备注 } export const upgradeLogList = ref([]) diff --git a/src/plugins/echarts.ts b/src/plugins/echarts.ts index 4f56d89..1c3bb17 100644 --- a/src/plugins/echarts.ts +++ b/src/plugins/echarts.ts @@ -13,28 +13,28 @@ import * as echarts from 'echarts/core' // 导入图表类型 import { - BarChart, - LineChart, - PieChart, - ScatterChart, - RadarChart, - MapChart, - CandlestickChart + BarChart, + LineChart, + PieChart, + ScatterChart, + RadarChart, + MapChart, + CandlestickChart } from 'echarts/charts' // 导入组件 import { - TitleComponent, - TooltipComponent, - GridComponent, - LegendComponent, - DataZoomComponent, - MarkPointComponent, - MarkLineComponent, - ToolboxComponent, - BrushComponent, - GeoComponent, - VisualMapComponent + TitleComponent, + TooltipComponent, + GridComponent, + LegendComponent, + DataZoomComponent, + MarkPointComponent, + MarkLineComponent, + ToolboxComponent, + BrushComponent, + GeoComponent, + VisualMapComponent } from 'echarts/components' // 导入渲染器 @@ -42,30 +42,30 @@ import { CanvasRenderer } from 'echarts/renderers' // 注册必要的组件 echarts.use([ - // 图表类型 - BarChart, - LineChart, - PieChart, - ScatterChart, - RadarChart, - MapChart, - CandlestickChart, + // 图表类型 + BarChart, + LineChart, + PieChart, + ScatterChart, + RadarChart, + MapChart, + CandlestickChart, - // 组件 - TitleComponent, - TooltipComponent, - GridComponent, - LegendComponent, - DataZoomComponent, - MarkPointComponent, - MarkLineComponent, - ToolboxComponent, - BrushComponent, - GeoComponent, - VisualMapComponent, + // 组件 + TitleComponent, + TooltipComponent, + GridComponent, + LegendComponent, + DataZoomComponent, + MarkPointComponent, + MarkLineComponent, + ToolboxComponent, + BrushComponent, + GeoComponent, + VisualMapComponent, - // 渲染器 - CanvasRenderer + // 渲染器 + CanvasRenderer ]) // 导出 echarts 实例和类型 diff --git a/src/router/core/ComponentLoader.ts b/src/router/core/ComponentLoader.ts index 8af3ce3..ce15077 100644 --- a/src/router/core/ComponentLoader.ts +++ b/src/router/core/ComponentLoader.ts @@ -10,73 +10,73 @@ import { h } from 'vue' export class ComponentLoader { - private modules: Record Promise> + private modules: Record Promise> - constructor() { - // 动态导入 views 目录下所有 .vue 组件 - this.modules = import.meta.glob('../../views/**/*.vue') - } + constructor() { + // 动态导入 views 目录下所有 .vue 组件 + this.modules = import.meta.glob('../../views/**/*.vue') + } - /** - * 加载组件 - */ - load(componentPath: string): () => Promise { - if (!componentPath) { - return this.createEmptyComponent() - } + /** + * 加载组件 + */ + load(componentPath: string): () => Promise { + if (!componentPath) { + return this.createEmptyComponent() + } - // 构建可能的路径 - const fullPath = `../../views${componentPath}.vue` - const fullPathWithIndex = `../../views${componentPath}/index.vue` + // 构建可能的路径 + const fullPath = `../../views${componentPath}.vue` + const fullPathWithIndex = `../../views${componentPath}/index.vue` - // 先尝试直接路径,再尝试添加/index的路径 - const module = this.modules[fullPath] || this.modules[fullPathWithIndex] + // 先尝试直接路径,再尝试添加/index的路径 + const module = this.modules[fullPath] || this.modules[fullPathWithIndex] - if (!module) { - console.error( - `[ComponentLoader] 未找到组件: ${componentPath},尝试过的路径: ${fullPath} 和 ${fullPathWithIndex}` - ) - return this.createErrorComponent(componentPath) - } + if (!module) { + console.error( + `[ComponentLoader] 未找到组件: ${componentPath},尝试过的路径: ${fullPath} 和 ${fullPathWithIndex}` + ) + return this.createErrorComponent(componentPath) + } - return module - } + return module + } - /** - * 加载布局组件 - */ - loadLayout(): () => Promise { - return () => import('@/views/index/index.vue') - } + /** + * 加载布局组件 + */ + loadLayout(): () => Promise { + return () => import('@/views/index/index.vue') + } - /** - * 加载 iframe 组件 - */ - loadIframe(): () => Promise { - return () => import('@/views/outside/Iframe.vue') - } + /** + * 加载 iframe 组件 + */ + loadIframe(): () => Promise { + return () => import('@/views/outside/Iframe.vue') + } - /** - * 创建空组件 - */ - private createEmptyComponent(): () => Promise { - return () => - Promise.resolve({ - render() { - return h('div', {}) - } - }) - } + /** + * 创建空组件 + */ + private createEmptyComponent(): () => Promise { + return () => + Promise.resolve({ + render() { + return h('div', {}) + } + }) + } - /** - * 创建错误提示组件 - */ - private createErrorComponent(componentPath: string): () => Promise { - return () => - Promise.resolve({ - render() { - return h('div', { class: 'route-error' }, `组件未找到: ${componentPath}`) - } - }) - } + /** + * 创建错误提示组件 + */ + private createErrorComponent(componentPath: string): () => Promise { + return () => + Promise.resolve({ + render() { + return h('div', { class: 'route-error' }, `组件未找到: ${componentPath}`) + } + }) + } } diff --git a/src/router/core/IframeRouteManager.ts b/src/router/core/IframeRouteManager.ts index c054ca1..4b38e87 100644 --- a/src/router/core/IframeRouteManager.ts +++ b/src/router/core/IframeRouteManager.ts @@ -10,69 +10,69 @@ import type { AppRouteRecord } from '@/types/router' export class IframeRouteManager { - private static instance: IframeRouteManager - private iframeRoutes: AppRouteRecord[] = [] + private static instance: IframeRouteManager + private iframeRoutes: AppRouteRecord[] = [] - private constructor() {} + private constructor() {} - static getInstance(): IframeRouteManager { - if (!IframeRouteManager.instance) { - IframeRouteManager.instance = new IframeRouteManager() - } - return IframeRouteManager.instance - } + static getInstance(): IframeRouteManager { + if (!IframeRouteManager.instance) { + IframeRouteManager.instance = new IframeRouteManager() + } + return IframeRouteManager.instance + } - /** - * 添加 iframe 路由 - */ - add(route: AppRouteRecord): void { - if (!this.iframeRoutes.find((r) => r.path === route.path)) { - this.iframeRoutes.push(route) - } - } + /** + * 添加 iframe 路由 + */ + add(route: AppRouteRecord): void { + if (!this.iframeRoutes.find((r) => r.path === route.path)) { + this.iframeRoutes.push(route) + } + } - /** - * 获取所有 iframe 路由 - */ - getAll(): AppRouteRecord[] { - return this.iframeRoutes - } + /** + * 获取所有 iframe 路由 + */ + getAll(): AppRouteRecord[] { + return this.iframeRoutes + } - /** - * 根据路径查找 iframe 路由 - */ - findByPath(path: string): AppRouteRecord | undefined { - return this.iframeRoutes.find((route) => route.path === path) - } + /** + * 根据路径查找 iframe 路由 + */ + findByPath(path: string): AppRouteRecord | undefined { + return this.iframeRoutes.find((route) => route.path === path) + } - /** - * 清空所有 iframe 路由 - */ - clear(): void { - this.iframeRoutes = [] - } + /** + * 清空所有 iframe 路由 + */ + clear(): void { + this.iframeRoutes = [] + } - /** - * 保存到 sessionStorage - */ - save(): void { - if (this.iframeRoutes.length > 0) { - sessionStorage.setItem('iframeRoutes', JSON.stringify(this.iframeRoutes)) - } - } + /** + * 保存到 sessionStorage + */ + save(): void { + if (this.iframeRoutes.length > 0) { + sessionStorage.setItem('iframeRoutes', JSON.stringify(this.iframeRoutes)) + } + } - /** - * 从 sessionStorage 加载 - */ - load(): void { - try { - const data = sessionStorage.getItem('iframeRoutes') - if (data) { - this.iframeRoutes = JSON.parse(data) - } - } catch (error) { - console.error('[IframeRouteManager] 加载 iframe 路由失败:', error) - this.iframeRoutes = [] - } - } + /** + * 从 sessionStorage 加载 + */ + load(): void { + try { + const data = sessionStorage.getItem('iframeRoutes') + if (data) { + this.iframeRoutes = JSON.parse(data) + } + } catch (error) { + console.error('[IframeRouteManager] 加载 iframe 路由失败:', error) + this.iframeRoutes = [] + } + } } diff --git a/src/router/core/MenuProcessor.ts b/src/router/core/MenuProcessor.ts index 57bf183..35905d1 100644 --- a/src/router/core/MenuProcessor.ts +++ b/src/router/core/MenuProcessor.ts @@ -16,226 +16,230 @@ import { RoutesAlias } from '../routesAlias' import { formatMenuTitle } from '@/utils' export class MenuProcessor { - /** - * 获取菜单数据 - */ - async getMenuList(): Promise { - const { isFrontendMode } = useAppMode() + /** + * 获取菜单数据 + */ + async getMenuList(): Promise { + const { isFrontendMode } = useAppMode() - let menuList: AppRouteRecord[] - if (isFrontendMode.value) { - menuList = await this.processFrontendMenu() - } else { - menuList = await this.processBackendMenu() - } + let menuList: AppRouteRecord[] + if (isFrontendMode.value) { + menuList = await this.processFrontendMenu() + } else { + menuList = await this.processBackendMenu() + } - // 在规范化路径之前,验证原始路径配置 - this.validateMenuPaths(menuList) + // 在规范化路径之前,验证原始路径配置 + this.validateMenuPaths(menuList) - // 规范化路径(将相对路径转换为完整路径) - return this.normalizeMenuPaths(menuList) - } + // 规范化路径(将相对路径转换为完整路径) + return this.normalizeMenuPaths(menuList) + } - /** - * 处理前端控制模式的菜单 - */ - private async processFrontendMenu(): Promise { - const userStore = useUserStore() - const roles = userStore.info?.roles + /** + * 处理前端控制模式的菜单 + */ + private async processFrontendMenu(): Promise { + const userStore = useUserStore() + const roles = userStore.info?.roles - let menuList = [...asyncRoutes] + let menuList = [...asyncRoutes] - // 根据角色过滤菜单 - if (roles && roles.length > 0) { - menuList = this.filterMenuByRoles(menuList, roles) - } + // 根据角色过滤菜单 + if (roles && roles.length > 0) { + menuList = this.filterMenuByRoles(menuList, roles) + } - return this.filterEmptyMenus(menuList) - } + return this.filterEmptyMenus(menuList) + } - /** - * 处理后端控制模式的菜单 - */ - private async processBackendMenu(): Promise { - const list = await fetchGetMenuList() - return this.filterEmptyMenus(list) - } + /** + * 处理后端控制模式的菜单 + */ + private async processBackendMenu(): Promise { + const list = await fetchGetMenuList() + return this.filterEmptyMenus(list) + } - /** - * 根据角色过滤菜单 - */ - private filterMenuByRoles(menu: AppRouteRecord[], roles: string[]): AppRouteRecord[] { - return menu.reduce((acc: AppRouteRecord[], item) => { - const itemRoles = item.meta?.roles - const hasPermission = !itemRoles || itemRoles.some((role) => roles?.includes(role)) + /** + * 根据角色过滤菜单 + */ + private filterMenuByRoles(menu: AppRouteRecord[], roles: string[]): AppRouteRecord[] { + return menu.reduce((acc: AppRouteRecord[], item) => { + const itemRoles = item.meta?.roles + const hasPermission = !itemRoles || itemRoles.some((role) => roles?.includes(role)) - if (hasPermission) { - const filteredItem = { ...item } - if (filteredItem.children?.length) { - filteredItem.children = this.filterMenuByRoles(filteredItem.children, roles) - } - acc.push(filteredItem) - } + if (hasPermission) { + const filteredItem = { ...item } + if (filteredItem.children?.length) { + filteredItem.children = this.filterMenuByRoles(filteredItem.children, roles) + } + acc.push(filteredItem) + } - return acc - }, []) - } + return acc + }, []) + } - /** - * 递归过滤空菜单项 - */ - private filterEmptyMenus(menuList: AppRouteRecord[]): AppRouteRecord[] { - return menuList - .map((item) => { - // 如果有子菜单,先递归过滤子菜单 - if (item.children && item.children.length > 0) { - const filteredChildren = this.filterEmptyMenus(item.children) - return { - ...item, - children: filteredChildren - } - } - return item - }) - .filter((item) => { - // 如果定义了 children 属性(即使是空数组),说明这是一个目录菜单,应该保留 - if ('children' in item) { - return true - } + /** + * 递归过滤空菜单项 + */ + private filterEmptyMenus(menuList: AppRouteRecord[]): AppRouteRecord[] { + return menuList + .map((item) => { + // 如果有子菜单,先递归过滤子菜单 + if (item.children && item.children.length > 0) { + const filteredChildren = this.filterEmptyMenus(item.children) + return { + ...item, + children: filteredChildren + } + } + return item + }) + .filter((item) => { + // 如果定义了 children 属性(即使是空数组),说明这是一个目录菜单,应该保留 + if ('children' in item) { + return true + } - // 如果有外链或 iframe,保留 - if (item.meta?.isIframe === true || item.meta?.link) { - return true - } + // 如果有外链或 iframe,保留 + if (item.meta?.isIframe === true || item.meta?.link) { + return true + } - // 如果有有效的 component,保留 - if (item.component && item.component !== '' && item.component !== RoutesAlias.Layout) { - return true - } + // 如果有有效的 component,保留 + if ( + item.component && + item.component !== '' && + item.component !== RoutesAlias.Layout + ) { + return true + } - // 其他情况过滤掉 - return false - }) - } + // 其他情况过滤掉 + return false + }) + } - /** - * 验证菜单列表是否有效 - */ - validateMenuList(menuList: AppRouteRecord[]): boolean { - return Array.isArray(menuList) && menuList.length > 0 - } + /** + * 验证菜单列表是否有效 + */ + validateMenuList(menuList: AppRouteRecord[]): boolean { + return Array.isArray(menuList) && menuList.length > 0 + } - /** - * 规范化菜单路径 - * 将相对路径转换为完整路径,确保菜单跳转正确 - */ - private normalizeMenuPaths(menuList: AppRouteRecord[], parentPath = ''): AppRouteRecord[] { - return menuList.map((item) => { - // 构建完整路径 - const fullPath = this.buildFullPath(item.path || '', parentPath) + /** + * 规范化菜单路径 + * 将相对路径转换为完整路径,确保菜单跳转正确 + */ + private normalizeMenuPaths(menuList: AppRouteRecord[], parentPath = ''): AppRouteRecord[] { + return menuList.map((item) => { + // 构建完整路径 + const fullPath = this.buildFullPath(item.path || '', parentPath) - // 递归处理子菜单 - const children = item.children?.length - ? this.normalizeMenuPaths(item.children, fullPath) - : item.children + // 递归处理子菜单 + const children = item.children?.length + ? this.normalizeMenuPaths(item.children, fullPath) + : item.children - return { - ...item, - path: fullPath, - children - } - }) - } + return { + ...item, + path: fullPath, + children + } + }) + } - /** - * 验证菜单路径配置 - * 检测非一级菜单是否错误使用了 / 开头的路径 - */ - /** - * 验证菜单路径配置 - * 检测非一级菜单是否错误使用了 / 开头的路径 - */ - private validateMenuPaths(menuList: AppRouteRecord[], level = 1): void { - menuList.forEach((route) => { - if (!route.children?.length) return + /** + * 验证菜单路径配置 + * 检测非一级菜单是否错误使用了 / 开头的路径 + */ + /** + * 验证菜单路径配置 + * 检测非一级菜单是否错误使用了 / 开头的路径 + */ + private validateMenuPaths(menuList: AppRouteRecord[], level = 1): void { + menuList.forEach((route) => { + if (!route.children?.length) return - const parentName = String(route.name || route.path || '未知路由') + const parentName = String(route.name || route.path || '未知路由') - route.children.forEach((child) => { - const childPath = child.path || '' + route.children.forEach((child) => { + const childPath = child.path || '' - // 跳过合法的绝对路径:外部链接和 iframe 路由 - if (this.isValidAbsolutePath(childPath)) return + // 跳过合法的绝对路径:外部链接和 iframe 路由 + if (this.isValidAbsolutePath(childPath)) return - // 检测非法的绝对路径 - if (childPath.startsWith('/')) { - this.logPathError(child, childPath, parentName, level) - } - }) + // 检测非法的绝对路径 + if (childPath.startsWith('/')) { + this.logPathError(child, childPath, parentName, level) + } + }) - // 递归检查更深层级的子路由 - this.validateMenuPaths(route.children, level + 1) - }) - } + // 递归检查更深层级的子路由 + this.validateMenuPaths(route.children, level + 1) + }) + } - /** - * 判断是否为合法的绝对路径 - */ - private isValidAbsolutePath(path: string): boolean { - return ( - path.startsWith('http://') || - path.startsWith('https://') || - path.startsWith('/outside/iframe/') - ) - } + /** + * 判断是否为合法的绝对路径 + */ + private isValidAbsolutePath(path: string): boolean { + return ( + path.startsWith('http://') || + path.startsWith('https://') || + path.startsWith('/outside/iframe/') + ) + } - /** - * 输出路径配置错误日志 - */ - private logPathError( - route: AppRouteRecord, - path: string, - parentName: string, - level: number - ): void { - const routeName = String(route.name || path || '未知路由') - const menuTitle = route.meta?.title || routeName - const suggestedPath = path.split('/').pop() || path.slice(1) + /** + * 输出路径配置错误日志 + */ + private logPathError( + route: AppRouteRecord, + path: string, + parentName: string, + level: number + ): void { + const routeName = String(route.name || path || '未知路由') + const menuTitle = route.meta?.title || routeName + const suggestedPath = path.split('/').pop() || path.slice(1) - console.error( - `[路由配置错误] 菜单 "${formatMenuTitle(menuTitle)}" (name: ${routeName}, path: ${path}) 配置错误\n` + - ` 位置: ${parentName} > ${routeName}\n` + - ` 问题: ${level + 1}级菜单的 path 不能以 / 开头\n` + - ` 当前配置: path: '${path}'\n` + - ` 应该改为: path: '${suggestedPath}'` - ) - } + console.error( + `[路由配置错误] 菜单 "${formatMenuTitle(menuTitle)}" (name: ${routeName}, path: ${path}) 配置错误\n` + + ` 位置: ${parentName} > ${routeName}\n` + + ` 问题: ${level + 1}级菜单的 path 不能以 / 开头\n` + + ` 当前配置: path: '${path}'\n` + + ` 应该改为: path: '${suggestedPath}'` + ) + } - /** - * 构建完整路径 - */ - private buildFullPath(path: string, parentPath: string): string { - if (!path) return '' + /** + * 构建完整路径 + */ + private buildFullPath(path: string, parentPath: string): string { + if (!path) return '' - // 外部链接直接返回 - if (path.startsWith('http://') || path.startsWith('https://')) { - return path - } + // 外部链接直接返回 + if (path.startsWith('http://') || path.startsWith('https://')) { + return path + } - // 如果已经是绝对路径,直接返回 - if (path.startsWith('/')) { - return path - } + // 如果已经是绝对路径,直接返回 + if (path.startsWith('/')) { + return path + } - // 拼接父路径和当前路径 - if (parentPath) { - // 移除父路径末尾的斜杠,移除子路径开头的斜杠,然后拼接 - const cleanParent = parentPath.replace(/\/$/, '') - const cleanChild = path.replace(/^\//, '') - return `${cleanParent}/${cleanChild}` - } + // 拼接父路径和当前路径 + if (parentPath) { + // 移除父路径末尾的斜杠,移除子路径开头的斜杠,然后拼接 + const cleanParent = parentPath.replace(/\/$/, '') + const cleanChild = path.replace(/^\//, '') + return `${cleanParent}/${cleanChild}` + } - // 没有父路径,添加前导斜杠 - return `/${path}` - } + // 没有父路径,添加前导斜杠 + return `/${path}` + } } diff --git a/src/router/core/RoutePermissionValidator.ts b/src/router/core/RoutePermissionValidator.ts index c33e663..63d3b06 100644 --- a/src/router/core/RoutePermissionValidator.ts +++ b/src/router/core/RoutePermissionValidator.ts @@ -26,94 +26,94 @@ import type { AppRouteRecord } from '@/types/router' * 路由权限验证器 */ export class RoutePermissionValidator { - /** - * 验证路径是否在用户菜单权限中 - * @param targetPath 目标路径 - * @param menuList 菜单列表 - * @returns 是否有权限访问 - */ - static hasPermission(targetPath: string, menuList: AppRouteRecord[]): boolean { - // 根路径始终允许访问 - if (targetPath === '/') { - return true - } + /** + * 验证路径是否在用户菜单权限中 + * @param targetPath 目标路径 + * @param menuList 菜单列表 + * @returns 是否有权限访问 + */ + static hasPermission(targetPath: string, menuList: AppRouteRecord[]): boolean { + // 根路径始终允许访问 + if (targetPath === '/') { + return true + } - // 构建路径集合 - const pathSet = this.buildMenuPathSet(menuList) + // 构建路径集合 + const pathSet = this.buildMenuPathSet(menuList) - // 检查路径是否在集合中(精确匹配或前缀匹配) - return pathSet.has(targetPath) || this.checkPathPrefix(targetPath, pathSet) - } + // 检查路径是否在集合中(精确匹配或前缀匹配) + return pathSet.has(targetPath) || this.checkPathPrefix(targetPath, pathSet) + } - /** - * 构建菜单路径集合(扁平化处理) - * @param menuList 菜单列表 - * @param pathSet 路径集合 - * @returns 路径集合 - */ - static buildMenuPathSet( - menuList: AppRouteRecord[], - pathSet: Set = new Set() - ): Set { - if (!Array.isArray(menuList) || menuList.length === 0) { - return pathSet - } + /** + * 构建菜单路径集合(扁平化处理) + * @param menuList 菜单列表 + * @param pathSet 路径集合 + * @returns 路径集合 + */ + static buildMenuPathSet( + menuList: AppRouteRecord[], + pathSet: Set = new Set() + ): Set { + if (!Array.isArray(menuList) || menuList.length === 0) { + return pathSet + } - for (const menuItem of menuList) { - // 跳过隐藏的菜单项 - if (menuItem.meta?.isHide || !menuItem.path) { - continue - } + for (const menuItem of menuList) { + // 跳过隐藏的菜单项 + if (menuItem.meta?.isHide || !menuItem.path) { + continue + } - // 标准化路径并添加到集合 - const menuPath = menuItem.path.startsWith('/') ? menuItem.path : `/${menuItem.path}` - pathSet.add(menuPath) + // 标准化路径并添加到集合 + const menuPath = menuItem.path.startsWith('/') ? menuItem.path : `/${menuItem.path}` + pathSet.add(menuPath) - // 递归处理子菜单 - if (menuItem.children?.length) { - this.buildMenuPathSet(menuItem.children, pathSet) - } - } + // 递归处理子菜单 + if (menuItem.children?.length) { + this.buildMenuPathSet(menuItem.children, pathSet) + } + } - return pathSet - } + return pathSet + } - /** - * 检查目标路径是否匹配集合中的某个路径前缀 - * 用于支持动态路由参数匹配,如 /user/123 匹配 /user - * @param targetPath 目标路径 - * @param pathSet 路径集合 - * @returns 是否匹配 - */ - static checkPathPrefix(targetPath: string, pathSet: Set): boolean { - // 遍历路径集合,检查是否有前缀匹配 - for (const menuPath of pathSet) { - if (targetPath.startsWith(`${menuPath}/`)) { - return true - } - } - return false - } + /** + * 检查目标路径是否匹配集合中的某个路径前缀 + * 用于支持动态路由参数匹配,如 /user/123 匹配 /user + * @param targetPath 目标路径 + * @param pathSet 路径集合 + * @returns 是否匹配 + */ + static checkPathPrefix(targetPath: string, pathSet: Set): boolean { + // 遍历路径集合,检查是否有前缀匹配 + for (const menuPath of pathSet) { + if (targetPath.startsWith(`${menuPath}/`)) { + return true + } + } + return false + } - /** - * 验证并返回有效的路径 - * 如果目标路径无权限,返回首页路径 - * @param targetPath 目标路径 - * @param menuList 菜单列表 - * @param homePath 首页路径 - * @returns 验证后的路径 - */ - static validatePath( - targetPath: string, - menuList: AppRouteRecord[], - homePath: string = '/' - ): { path: string; hasPermission: boolean } { - const hasPermission = this.hasPermission(targetPath, menuList) + /** + * 验证并返回有效的路径 + * 如果目标路径无权限,返回首页路径 + * @param targetPath 目标路径 + * @param menuList 菜单列表 + * @param homePath 首页路径 + * @returns 验证后的路径 + */ + static validatePath( + targetPath: string, + menuList: AppRouteRecord[], + homePath: string = '/' + ): { path: string; hasPermission: boolean } { + const hasPermission = this.hasPermission(targetPath, menuList) - if (hasPermission) { - return { path: targetPath, hasPermission: true } - } + if (hasPermission) { + return { path: targetPath, hasPermission: true } + } - return { path: homePath, hasPermission: false } - } + return { path: homePath, hasPermission: false } + } } diff --git a/src/router/core/RouteRegistry.ts b/src/router/core/RouteRegistry.ts index e1acb9e..3e4e49b 100644 --- a/src/router/core/RouteRegistry.ts +++ b/src/router/core/RouteRegistry.ts @@ -14,77 +14,77 @@ import { RouteValidator } from './RouteValidator' import { RouteTransformer } from './RouteTransformer' export class RouteRegistry { - private router: Router - private componentLoader: ComponentLoader - private validator: RouteValidator - private transformer: RouteTransformer - private removeRouteFns: (() => void)[] = [] - private registered = false + private router: Router + private componentLoader: ComponentLoader + private validator: RouteValidator + private transformer: RouteTransformer + private removeRouteFns: (() => void)[] = [] + private registered = false - constructor(router: Router) { - this.router = router - this.componentLoader = new ComponentLoader() - this.validator = new RouteValidator() - this.transformer = new RouteTransformer(this.componentLoader) - } + constructor(router: Router) { + this.router = router + this.componentLoader = new ComponentLoader() + this.validator = new RouteValidator() + this.transformer = new RouteTransformer(this.componentLoader) + } - /** - * 注册动态路由 - */ - register(menuList: AppRouteRecord[]): void { - if (this.registered) { - console.warn('[RouteRegistry] 路由已注册,跳过重复注册') - return - } + /** + * 注册动态路由 + */ + register(menuList: AppRouteRecord[]): void { + if (this.registered) { + console.warn('[RouteRegistry] 路由已注册,跳过重复注册') + return + } - // 验证路由配置 - const validationResult = this.validator.validate(menuList) - if (!validationResult.valid) { - throw new Error(`路由配置验证失败: ${validationResult.errors.join(', ')}`) - } + // 验证路由配置 + const validationResult = this.validator.validate(menuList) + if (!validationResult.valid) { + throw new Error(`路由配置验证失败: ${validationResult.errors.join(', ')}`) + } - // 转换并注册路由 - const removeRouteFns: (() => void)[] = [] + // 转换并注册路由 + const removeRouteFns: (() => void)[] = [] - menuList.forEach((route) => { - if (route.name && !this.router.hasRoute(route.name)) { - const routeConfig = this.transformer.transform(route) - const removeRouteFn = this.router.addRoute(routeConfig as RouteRecordRaw) - removeRouteFns.push(removeRouteFn) - } - }) + menuList.forEach((route) => { + if (route.name && !this.router.hasRoute(route.name)) { + const routeConfig = this.transformer.transform(route) + const removeRouteFn = this.router.addRoute(routeConfig as RouteRecordRaw) + removeRouteFns.push(removeRouteFn) + } + }) - this.removeRouteFns = removeRouteFns - this.registered = true - } + this.removeRouteFns = removeRouteFns + this.registered = true + } - /** - * 移除所有动态路由 - */ - unregister(): void { - this.removeRouteFns.forEach((fn) => fn()) - this.removeRouteFns = [] - this.registered = false - } + /** + * 移除所有动态路由 + */ + unregister(): void { + this.removeRouteFns.forEach((fn) => fn()) + this.removeRouteFns = [] + this.registered = false + } - /** - * 检查是否已注册 - */ - isRegistered(): boolean { - return this.registered - } + /** + * 检查是否已注册 + */ + isRegistered(): boolean { + return this.registered + } - /** - * 获取移除函数列表(用于 store 管理) - */ - getRemoveRouteFns(): (() => void)[] { - return this.removeRouteFns - } + /** + * 获取移除函数列表(用于 store 管理) + */ + getRemoveRouteFns(): (() => void)[] { + return this.removeRouteFns + } - /** - * 标记为已注册(用于错误处理场景,避免重复请求) - */ - markAsRegistered(): void { - this.registered = true - } + /** + * 标记为已注册(用于错误处理场景,避免重复请求) + */ + markAsRegistered(): void { + this.registered = true + } } diff --git a/src/router/core/RouteTransformer.ts b/src/router/core/RouteTransformer.ts index 0f6900c..83522f3 100644 --- a/src/router/core/RouteTransformer.ts +++ b/src/router/core/RouteTransformer.ts @@ -13,120 +13,120 @@ import { ComponentLoader } from './ComponentLoader' import { IframeRouteManager } from './IframeRouteManager' interface ConvertedRoute extends Omit { - id?: number - children?: ConvertedRoute[] - component?: RouteRecordRaw['component'] | (() => Promise) + id?: number + children?: ConvertedRoute[] + component?: RouteRecordRaw['component'] | (() => Promise) } export class RouteTransformer { - private componentLoader: ComponentLoader - private iframeManager: IframeRouteManager + private componentLoader: ComponentLoader + private iframeManager: IframeRouteManager - constructor(componentLoader: ComponentLoader) { - this.componentLoader = componentLoader - this.iframeManager = IframeRouteManager.getInstance() - } + constructor(componentLoader: ComponentLoader) { + this.componentLoader = componentLoader + this.iframeManager = IframeRouteManager.getInstance() + } - /** - * 转换路由配置 - */ - transform(route: AppRouteRecord, depth = 0): ConvertedRoute { - const { component, children, ...routeConfig } = route + /** + * 转换路由配置 + */ + transform(route: AppRouteRecord, depth = 0): ConvertedRoute { + const { component, children, ...routeConfig } = route - // 基础路由配置 - const converted: ConvertedRoute = { - ...routeConfig, - component: undefined - } + // 基础路由配置 + const converted: ConvertedRoute = { + ...routeConfig, + component: undefined + } - // 处理不同类型的路由 - if (route.meta.isIframe) { - this.handleIframeRoute(converted, route, depth) - } else if (this.isFirstLevelRoute(route, depth)) { - this.handleFirstLevelRoute(converted, route, component as string) - } else { - this.handleNormalRoute(converted, component as string) - } + // 处理不同类型的路由 + if (route.meta.isIframe) { + this.handleIframeRoute(converted, route, depth) + } else if (this.isFirstLevelRoute(route, depth)) { + this.handleFirstLevelRoute(converted, route, component as string) + } else { + this.handleNormalRoute(converted, component as string) + } - // 递归处理子路由 - if (children?.length) { - converted.children = children.map((child) => this.transform(child, depth + 1)) - } + // 递归处理子路由 + if (children?.length) { + converted.children = children.map((child) => this.transform(child, depth + 1)) + } - return converted - } + return converted + } - /** - * 判断是否为一级路由(需要 Layout 包裹) - */ - private isFirstLevelRoute(route: AppRouteRecord, depth: number): boolean { - return depth === 0 && (!route.children || route.children.length === 0) - } + /** + * 判断是否为一级路由(需要 Layout 包裹) + */ + private isFirstLevelRoute(route: AppRouteRecord, depth: number): boolean { + return depth === 0 && (!route.children || route.children.length === 0) + } - /** - * 处理 iframe 类型路由 - */ - private handleIframeRoute( - targetRoute: ConvertedRoute, - sourceRoute: AppRouteRecord, - depth: number - ): void { - if (depth === 0) { - // 顶级 iframe:用 Layout 包裹 - targetRoute.component = this.componentLoader.loadLayout() - targetRoute.path = this.extractFirstSegment(sourceRoute.path || '') - targetRoute.name = '' + /** + * 处理 iframe 类型路由 + */ + private handleIframeRoute( + targetRoute: ConvertedRoute, + sourceRoute: AppRouteRecord, + depth: number + ): void { + if (depth === 0) { + // 顶级 iframe:用 Layout 包裹 + targetRoute.component = this.componentLoader.loadLayout() + targetRoute.path = this.extractFirstSegment(sourceRoute.path || '') + targetRoute.name = '' - targetRoute.children = [ - { - ...sourceRoute, - component: this.componentLoader.loadIframe() - } as ConvertedRoute - ] - } else { - // 非顶级(嵌套)iframe:直接使用 Iframe.vue - targetRoute.component = this.componentLoader.loadIframe() - } + targetRoute.children = [ + { + ...sourceRoute, + component: this.componentLoader.loadIframe() + } as ConvertedRoute + ] + } else { + // 非顶级(嵌套)iframe:直接使用 Iframe.vue + targetRoute.component = this.componentLoader.loadIframe() + } - // 记录 iframe 路由 - this.iframeManager.add(sourceRoute) - } + // 记录 iframe 路由 + this.iframeManager.add(sourceRoute) + } - /** - * 处理一级菜单路由 - */ - private handleFirstLevelRoute( - converted: ConvertedRoute, - route: AppRouteRecord, - component: string | undefined - ): void { - converted.component = this.componentLoader.loadLayout() - converted.path = this.extractFirstSegment(route.path || '') - converted.name = '' - route.meta.isFirstLevel = true + /** + * 处理一级菜单路由 + */ + private handleFirstLevelRoute( + converted: ConvertedRoute, + route: AppRouteRecord, + component: string | undefined + ): void { + converted.component = this.componentLoader.loadLayout() + converted.path = this.extractFirstSegment(route.path || '') + converted.name = '' + route.meta.isFirstLevel = true - converted.children = [ - { - ...route, - component: component ? this.componentLoader.load(component) : undefined - } as ConvertedRoute - ] - } + converted.children = [ + { + ...route, + component: component ? this.componentLoader.load(component) : undefined + } as ConvertedRoute + ] + } - /** - * 处理普通路由 - */ - private handleNormalRoute(converted: ConvertedRoute, component: string | undefined): void { - if (component) { - converted.component = this.componentLoader.load(component) - } - } + /** + * 处理普通路由 + */ + private handleNormalRoute(converted: ConvertedRoute, component: string | undefined): void { + if (component) { + converted.component = this.componentLoader.load(component) + } + } - /** - * 提取路径的第一段 - */ - private extractFirstSegment(path: string): string { - const segments = path.split('/').filter(Boolean) - return segments.length > 0 ? `/${segments[0]}` : '/' - } + /** + * 提取路径的第一段 + */ + private extractFirstSegment(path: string): string { + const segments = path.split('/').filter(Boolean) + return segments.length > 0 ? `/${segments[0]}` : '/' + } } diff --git a/src/router/core/RouteValidator.ts b/src/router/core/RouteValidator.ts index f8e58fc..b4eaeda 100644 --- a/src/router/core/RouteValidator.ts +++ b/src/router/core/RouteValidator.ts @@ -11,177 +11,177 @@ import type { AppRouteRecord } from '@/types/router' import { RoutesAlias } from '../routesAlias' export interface ValidationResult { - valid: boolean - errors: string[] - warnings: string[] + valid: boolean + errors: string[] + warnings: string[] } export class RouteValidator { - // 用于记录已经提示过的路由,避免重复提示 - private warnedRoutes = new Set() + // 用于记录已经提示过的路由,避免重复提示 + private warnedRoutes = new Set() - /** - * 验证路由配置 - */ - validate(routes: AppRouteRecord[]): ValidationResult { - const errors: string[] = [] - const warnings: string[] = [] + /** + * 验证路由配置 + */ + validate(routes: AppRouteRecord[]): ValidationResult { + const errors: string[] = [] + const warnings: string[] = [] - // 检测重复路由 - this.checkDuplicates(routes, errors, warnings) + // 检测重复路由 + this.checkDuplicates(routes, errors, warnings) - // 检测组件配置 - this.checkComponents(routes, errors, warnings) + // 检测组件配置 + this.checkComponents(routes, errors, warnings) - // 检测嵌套菜单的 /index/index 配置 - this.checkNestedIndexComponent(routes) + // 检测嵌套菜单的 /index/index 配置 + this.checkNestedIndexComponent(routes) - return { - valid: errors.length === 0, - errors, - warnings - } - } + return { + valid: errors.length === 0, + errors, + warnings + } + } - /** - * 检测重复路由 - */ - private checkDuplicates( - routes: AppRouteRecord[], - errors: string[], - warnings: string[], - parentPath = '' - ): void { - const routeNameMap = new Map() - const componentPathMap = new Map() + /** + * 检测重复路由 + */ + private checkDuplicates( + routes: AppRouteRecord[], + errors: string[], + warnings: string[], + parentPath = '' + ): void { + const routeNameMap = new Map() + const componentPathMap = new Map() - const checkRoutes = (routes: AppRouteRecord[], parentPath = '') => { - routes.forEach((route) => { - const currentPath = route.path || '' - const fullPath = this.resolvePath(parentPath, currentPath) + const checkRoutes = (routes: AppRouteRecord[], parentPath = '') => { + routes.forEach((route) => { + const currentPath = route.path || '' + const fullPath = this.resolvePath(parentPath, currentPath) - // 名称重复检测 - if (route.name) { - const routeName = String(route.name) - if (routeNameMap.has(routeName)) { - warnings.push(`路由名称重复: "${routeName}" (${fullPath})`) - } else { - routeNameMap.set(routeName, fullPath) - } - } + // 名称重复检测 + if (route.name) { + const routeName = String(route.name) + if (routeNameMap.has(routeName)) { + warnings.push(`路由名称重复: "${routeName}" (${fullPath})`) + } else { + routeNameMap.set(routeName, fullPath) + } + } - // 组件路径重复检测 - if (route.component && typeof route.component === 'string') { - const componentPath = route.component - if (componentPath !== RoutesAlias.Layout) { - const componentKey = `${parentPath}:${componentPath}` - if (componentPathMap.has(componentKey)) { - warnings.push(`组件路径重复: "${componentPath}" (${fullPath})`) - } else { - componentPathMap.set(componentKey, fullPath) - } - } - } + // 组件路径重复检测 + if (route.component && typeof route.component === 'string') { + const componentPath = route.component + if (componentPath !== RoutesAlias.Layout) { + const componentKey = `${parentPath}:${componentPath}` + if (componentPathMap.has(componentKey)) { + warnings.push(`组件路径重复: "${componentPath}" (${fullPath})`) + } else { + componentPathMap.set(componentKey, fullPath) + } + } + } - // 递归处理子路由 - if (route.children?.length) { - checkRoutes(route.children, fullPath) - } - }) - } + // 递归处理子路由 + if (route.children?.length) { + checkRoutes(route.children, fullPath) + } + }) + } - checkRoutes(routes, parentPath) - } + checkRoutes(routes, parentPath) + } - /** - * 检测组件配置 - */ - private checkComponents( - routes: AppRouteRecord[], - errors: string[], - warnings: string[], - parentPath = '' - ): void { - routes.forEach((route) => { - const hasExternalLink = !!route.meta?.link?.trim() - const hasChildren = Array.isArray(route.children) && route.children.length > 0 - const routePath = route.path || '[未定义路径]' - const isIframe = route.meta?.isIframe + /** + * 检测组件配置 + */ + private checkComponents( + routes: AppRouteRecord[], + errors: string[], + warnings: string[], + parentPath = '' + ): void { + routes.forEach((route) => { + const hasExternalLink = !!route.meta?.link?.trim() + const hasChildren = Array.isArray(route.children) && route.children.length > 0 + const routePath = route.path || '[未定义路径]' + const isIframe = route.meta?.isIframe - // 如果配置了 component,则无需校验 - if (route.component) { - // 递归检查子路由 - if (route.children?.length) { - const fullPath = this.resolvePath(parentPath, route.path || '') - this.checkComponents(route.children, errors, warnings, fullPath) - } - return - } + // 如果配置了 component,则无需校验 + if (route.component) { + // 递归检查子路由 + if (route.children?.length) { + const fullPath = this.resolvePath(parentPath, route.path || '') + this.checkComponents(route.children, errors, warnings, fullPath) + } + return + } - // 一级菜单:必须指定 Layout,除非是外链或 iframe - if (parentPath === '' && !hasExternalLink && !isIframe) { - errors.push(`一级菜单(${routePath}) 缺少 component,必须指向 ${RoutesAlias.Layout}`) - return - } + // 一级菜单:必须指定 Layout,除非是外链或 iframe + if (parentPath === '' && !hasExternalLink && !isIframe) { + errors.push(`一级菜单(${routePath}) 缺少 component,必须指向 ${RoutesAlias.Layout}`) + return + } - // 非一级菜单:如果既不是外链、iframe,也没有子路由,则必须配置 component - if (!hasExternalLink && !isIframe && !hasChildren) { - errors.push(`路由(${routePath}) 缺少 component 配置`) - } + // 非一级菜单:如果既不是外链、iframe,也没有子路由,则必须配置 component + if (!hasExternalLink && !isIframe && !hasChildren) { + errors.push(`路由(${routePath}) 缺少 component 配置`) + } - // 递归检查子路由 - if (route.children?.length) { - const fullPath = this.resolvePath(parentPath, route.path || '') - this.checkComponents(route.children, errors, warnings, fullPath) - } - }) - } + // 递归检查子路由 + if (route.children?.length) { + const fullPath = this.resolvePath(parentPath, route.path || '') + this.checkComponents(route.children, errors, warnings, fullPath) + } + }) + } - /** - * 检测嵌套菜单的 Layout 组件配置 - * 只有一级菜单才能使用 Layout,二级及以下菜单不能使用 - */ - private checkNestedIndexComponent(routes: AppRouteRecord[], level = 1): void { - routes.forEach((route) => { - // 检查二级及以下菜单是否错误使用了 Layout - if (level > 1 && route.component === RoutesAlias.Layout) { - this.logLayoutError(route, level) - } + /** + * 检测嵌套菜单的 Layout 组件配置 + * 只有一级菜单才能使用 Layout,二级及以下菜单不能使用 + */ + private checkNestedIndexComponent(routes: AppRouteRecord[], level = 1): void { + routes.forEach((route) => { + // 检查二级及以下菜单是否错误使用了 Layout + if (level > 1 && route.component === RoutesAlias.Layout) { + this.logLayoutError(route, level) + } - // 递归检查子路由 - if (route.children?.length) { - this.checkNestedIndexComponent(route.children, level + 1) - } - }) - } + // 递归检查子路由 + if (route.children?.length) { + this.checkNestedIndexComponent(route.children, level + 1) + } + }) + } - /** - * 输出 Layout 组件配置错误日志 - */ - private logLayoutError(route: AppRouteRecord, level: number): void { - const routeName = String(route.name || route.path || '未知路由') - const routeKey = `${routeName}_${route.path}` + /** + * 输出 Layout 组件配置错误日志 + */ + private logLayoutError(route: AppRouteRecord, level: number): void { + const routeName = String(route.name || route.path || '未知路由') + const routeKey = `${routeName}_${route.path}` - // 避免重复提示 - if (this.warnedRoutes.has(routeKey)) return - this.warnedRoutes.add(routeKey) + // 避免重复提示 + if (this.warnedRoutes.has(routeKey)) return + this.warnedRoutes.add(routeKey) - const menuTitle = route.meta?.title || routeName - const routePath = route.path || '/' + const menuTitle = route.meta?.title || routeName + const routePath = route.path || '/' - console.error( - `[路由配置错误] 菜单 "${menuTitle}" (name: ${routeName}, path: ${routePath}) 配置错误\n` + - ` 问题: ${level}级菜单不能使用 ${RoutesAlias.Layout} 作为 component\n` + - ` 说明: 只有一级菜单才能使用 ${RoutesAlias.Layout},二级及以下菜单应该指向具体的组件路径\n` + - ` 当前配置: component: '${RoutesAlias.Layout}'\n` + - ` 应该改为: component: '/your/component/path' 或留空 ''(如果是目录菜单)` - ) - } + console.error( + `[路由配置错误] 菜单 "${menuTitle}" (name: ${routeName}, path: ${routePath}) 配置错误\n` + + ` 问题: ${level}级菜单不能使用 ${RoutesAlias.Layout} 作为 component\n` + + ` 说明: 只有一级菜单才能使用 ${RoutesAlias.Layout},二级及以下菜单应该指向具体的组件路径\n` + + ` 当前配置: component: '${RoutesAlias.Layout}'\n` + + ` 应该改为: component: '/your/component/path' 或留空 ''(如果是目录菜单)` + ) + } - /** - * 路径解析 - */ - private resolvePath(parent: string, child: string): string { - return [parent.replace(/\/$/, ''), child.replace(/^\//, '')].filter(Boolean).join('/') - } + /** + * 路径解析 + */ + private resolvePath(parent: string, child: string): string { + return [parent.replace(/\/$/, ''), child.replace(/^\//, '')].filter(Boolean).join('/') + } } diff --git a/src/router/guards/afterEach.ts b/src/router/guards/afterEach.ts index d60572d..ac3a232 100644 --- a/src/router/guards/afterEach.ts +++ b/src/router/guards/afterEach.ts @@ -8,27 +8,27 @@ import { getPendingLoading, resetPendingLoading } from './beforeEach' /** 路由全局后置守卫 */ export function setupAfterEachGuard(router: Router) { - const { scrollToTop } = useCommon() + const { scrollToTop } = useCommon() - router.afterEach(() => { - scrollToTop() + router.afterEach(() => { + scrollToTop() - // 关闭进度条 - const settingStore = useSettingStore() - if (settingStore.showNprogress) { - NProgress.done() - // 确保进度条完全移除,避免残影 - setTimeout(() => { - NProgress.remove() - }, 600) - } + // 关闭进度条 + const settingStore = useSettingStore() + if (settingStore.showNprogress) { + NProgress.done() + // 确保进度条完全移除,避免残影 + setTimeout(() => { + NProgress.remove() + }, 600) + } - // 关闭 loading 效果 - if (getPendingLoading()) { - nextTick(() => { - loadingService.hideLoading() - resetPendingLoading() - }) - } - }) + // 关闭 loading 效果 + if (getPendingLoading()) { + nextTick(() => { + loadingService.hideLoading() + resetPendingLoading() + }) + } + }) } diff --git a/src/router/guards/beforeEach.ts b/src/router/guards/beforeEach.ts index e94f633..58e1deb 100644 --- a/src/router/guards/beforeEach.ts +++ b/src/router/guards/beforeEach.ts @@ -73,128 +73,128 @@ let routeInitInProgress = false * 获取 pendingLoading 状态 */ export function getPendingLoading(): boolean { - return pendingLoading + return pendingLoading } /** * 重置 pendingLoading 状态 */ export function resetPendingLoading(): void { - pendingLoading = false + pendingLoading = false } /** * 获取路由初始化失败状态 */ export function getRouteInitFailed(): boolean { - return routeInitFailed + return routeInitFailed } /** * 重置路由初始化状态(用于重新登录场景) */ export function resetRouteInitState(): void { - routeInitFailed = false - routeInitInProgress = false + routeInitFailed = false + routeInitInProgress = false } /** * 设置路由全局前置守卫 */ export function setupBeforeEachGuard(router: Router): void { - // 初始化路由注册器 - routeRegistry = new RouteRegistry(router) + // 初始化路由注册器 + routeRegistry = new RouteRegistry(router) - router.beforeEach( - async ( - to: RouteLocationNormalized, - from: RouteLocationNormalized, - next: NavigationGuardNext - ) => { - try { - await handleRouteGuard(to, from, next, router) - } catch (error) { - console.error('[RouteGuard] 路由守卫处理失败:', error) - closeLoading() - next({ name: 'Exception500' }) - } - } - ) + router.beforeEach( + async ( + to: RouteLocationNormalized, + from: RouteLocationNormalized, + next: NavigationGuardNext + ) => { + try { + await handleRouteGuard(to, from, next, router) + } catch (error) { + console.error('[RouteGuard] 路由守卫处理失败:', error) + closeLoading() + next({ name: 'Exception500' }) + } + } + ) } /** * 关闭 loading 效果 */ function closeLoading(): void { - if (pendingLoading) { - nextTick(() => { - loadingService.hideLoading() - pendingLoading = false - }) - } + if (pendingLoading) { + nextTick(() => { + loadingService.hideLoading() + pendingLoading = false + }) + } } /** * 处理路由守卫逻辑 */ async function handleRouteGuard( - to: RouteLocationNormalized, - from: RouteLocationNormalized, - next: NavigationGuardNext, - router: Router + to: RouteLocationNormalized, + from: RouteLocationNormalized, + next: NavigationGuardNext, + router: Router ): Promise { - const settingStore = useSettingStore() - const userStore = useUserStore() + const settingStore = useSettingStore() + const userStore = useUserStore() - // 启动进度条 - if (settingStore.showNprogress) { - NProgress.start() - } + // 启动进度条 + if (settingStore.showNprogress) { + NProgress.start() + } - // 1. 检查登录状态 - if (!handleLoginStatus(to, userStore, next)) { - return - } + // 1. 检查登录状态 + if (!handleLoginStatus(to, userStore, next)) { + return + } - // 2. 检查路由初始化是否已失败(防止死循环) - if (routeInitFailed) { - // 已经失败过,直接放行到错误页面,不再重试 - if (to.matched.length > 0) { - next() - } else { - // 未匹配到路由,跳转到 500 页面 - next({ name: 'Exception500', replace: true }) - } - return - } + // 2. 检查路由初始化是否已失败(防止死循环) + if (routeInitFailed) { + // 已经失败过,直接放行到错误页面,不再重试 + if (to.matched.length > 0) { + next() + } else { + // 未匹配到路由,跳转到 500 页面 + next({ name: 'Exception500', replace: true }) + } + return + } - // 3. 处理动态路由注册 - if (!routeRegistry?.isRegistered() && userStore.isLogin) { - // 防止并发请求(快速连续导航场景) - if (routeInitInProgress) { - // 正在初始化中,等待完成后重新导航 - next(false) - return - } - await handleDynamicRoutes(to, next, router) - return - } + // 3. 处理动态路由注册 + if (!routeRegistry?.isRegistered() && userStore.isLogin) { + // 防止并发请求(快速连续导航场景) + if (routeInitInProgress) { + // 正在初始化中,等待完成后重新导航 + next(false) + return + } + await handleDynamicRoutes(to, next, router) + return + } - // 4. 处理根路径重定向 - if (handleRootPathRedirect(to, next)) { - return - } + // 4. 处理根路径重定向 + if (handleRootPathRedirect(to, next)) { + return + } - // 5. 处理已匹配的路由 - if (to.matched.length > 0) { - setWorktab(to) - setPageTitle(to) - next() - return - } + // 5. 处理已匹配的路由 + if (to.matched.length > 0) { + setWorktab(to) + setPageTitle(to) + next() + return + } - // 6. 未匹配到路由,跳转到 404 - next({ name: 'Exception404' }) + // 6. 未匹配到路由,跳转到 404 + next({ name: 'Exception404' }) } /** @@ -202,176 +202,176 @@ async function handleRouteGuard( * @returns true 表示可以继续,false 表示已处理跳转 */ function handleLoginStatus( - to: RouteLocationNormalized, - userStore: ReturnType, - next: NavigationGuardNext + to: RouteLocationNormalized, + userStore: ReturnType, + next: NavigationGuardNext ): boolean { - // 已登录或访问登录页或静态路由,直接放行 - if (userStore.isLogin || to.path === RoutesAlias.Login || isStaticRoute(to.path)) { - return true - } + // 已登录或访问登录页或静态路由,直接放行 + if (userStore.isLogin || to.path === RoutesAlias.Login || isStaticRoute(to.path)) { + return true + } - // 未登录且访问需要权限的页面,跳转到登录页并携带 redirect 参数 - userStore.logOut() - next({ - name: 'Login', - query: { redirect: to.fullPath } - }) - return false + // 未登录且访问需要权限的页面,跳转到登录页并携带 redirect 参数 + userStore.logOut() + next({ + name: 'Login', + query: { redirect: to.fullPath } + }) + return false } /** * 检查路由是否为静态路由 */ function isStaticRoute(path: string): boolean { - const checkRoute = (routes: any[], targetPath: string): boolean => { - return routes.some((route) => { - // 处理动态路由参数匹配 - const routePath = route.path - const pattern = routePath.replace(/:[^/]+/g, '[^/]+').replace(/\*/g, '.*') - const regex = new RegExp(`^${pattern}$`) + const checkRoute = (routes: any[], targetPath: string): boolean => { + return routes.some((route) => { + // 处理动态路由参数匹配 + const routePath = route.path + const pattern = routePath.replace(/:[^/]+/g, '[^/]+').replace(/\*/g, '.*') + const regex = new RegExp(`^${pattern}$`) - if (regex.test(targetPath)) { - return true - } - if (route.children && route.children.length > 0) { - return checkRoute(route.children, targetPath) - } - return false - }) - } + if (regex.test(targetPath)) { + return true + } + if (route.children && route.children.length > 0) { + return checkRoute(route.children, targetPath) + } + return false + }) + } - return checkRoute(staticRoutes, path) + return checkRoute(staticRoutes, path) } /** * 处理动态路由注册 */ async function handleDynamicRoutes( - to: RouteLocationNormalized, - next: NavigationGuardNext, - router: Router + to: RouteLocationNormalized, + next: NavigationGuardNext, + router: Router ): Promise { - // 标记初始化进行中 - routeInitInProgress = true + // 标记初始化进行中 + routeInitInProgress = true - // 显示 loading - pendingLoading = true - loadingService.showLoading() + // 显示 loading + pendingLoading = true + loadingService.showLoading() - try { - // 1. 获取用户信息 - await fetchUserInfo() + try { + // 1. 获取用户信息 + await fetchUserInfo() - // 2. 获取菜单数据 - const menuList = await menuProcessor.getMenuList() + // 2. 获取菜单数据 + const menuList = await menuProcessor.getMenuList() - // 3. 验证菜单数据 - if (!menuProcessor.validateMenuList(menuList)) { - throw new Error('获取菜单列表失败,请重新登录') - } + // 3. 验证菜单数据 + if (!menuProcessor.validateMenuList(menuList)) { + throw new Error('获取菜单列表失败,请重新登录') + } - // 4. 注册动态路由 - routeRegistry?.register(menuList) + // 4. 注册动态路由 + routeRegistry?.register(menuList) - // 5. 保存菜单数据到 store - const menuStore = useMenuStore() - menuStore.setMenuList(menuList) - menuStore.addRemoveRouteFns(routeRegistry?.getRemoveRouteFns() || []) + // 5. 保存菜单数据到 store + const menuStore = useMenuStore() + menuStore.setMenuList(menuList) + menuStore.addRemoveRouteFns(routeRegistry?.getRemoveRouteFns() || []) - // 6. 保存 iframe 路由 - IframeRouteManager.getInstance().save() + // 6. 保存 iframe 路由 + IframeRouteManager.getInstance().save() - // 7. 验证工作标签页 - useWorktabStore().validateWorktabs(router) + // 7. 验证工作标签页 + useWorktabStore().validateWorktabs(router) - // 8. 验证目标路径权限 - const { homePath } = useCommon() - const { path: validatedPath, hasPermission } = RoutePermissionValidator.validatePath( - to.path, - menuList, - homePath.value || '/' - ) + // 8. 验证目标路径权限 + const { homePath } = useCommon() + const { path: validatedPath, hasPermission } = RoutePermissionValidator.validatePath( + to.path, + menuList, + homePath.value || '/' + ) - // 初始化成功,重置进行中标记 - routeInitInProgress = false + // 初始化成功,重置进行中标记 + routeInitInProgress = false - // 9. 重新导航到目标路由 - if (!hasPermission) { - // 无权限访问,跳转到首页 - closeLoading() + // 9. 重新导航到目标路由 + if (!hasPermission) { + // 无权限访问,跳转到首页 + closeLoading() - // 输出警告信息 - console.warn(`[RouteGuard] 用户无权限访问路径: ${to.path},已跳转到首页`) + // 输出警告信息 + console.warn(`[RouteGuard] 用户无权限访问路径: ${to.path},已跳转到首页`) - // 直接跳转到首页 - next({ - path: validatedPath, - replace: true - }) - } else { - // 有权限,正常导航 - next({ - path: to.path, - query: to.query, - hash: to.hash, - replace: true - }) - } - } catch (error) { - console.error('[RouteGuard] 动态路由注册失败:', error) + // 直接跳转到首页 + next({ + path: validatedPath, + replace: true + }) + } else { + // 有权限,正常导航 + next({ + path: to.path, + query: to.query, + hash: to.hash, + replace: true + }) + } + } catch (error) { + console.error('[RouteGuard] 动态路由注册失败:', error) - // 关闭 loading - closeLoading() + // 关闭 loading + closeLoading() - // 401 错误:axios 拦截器已处理退出登录,取消当前导航 - if (isUnauthorizedError(error)) { - // 重置状态,允许重新登录后再次初始化 - routeInitInProgress = false - next(false) - return - } + // 401 错误:axios 拦截器已处理退出登录,取消当前导航 + if (isUnauthorizedError(error)) { + // 重置状态,允许重新登录后再次初始化 + routeInitInProgress = false + next(false) + return + } - // 标记初始化失败,防止死循环 - routeInitFailed = true - routeInitInProgress = false + // 标记初始化失败,防止死循环 + routeInitFailed = true + routeInitInProgress = false - // 输出详细错误信息,便于排查 - if (isHttpError(error)) { - console.error(`[RouteGuard] 错误码: ${error.code}, 消息: ${error.message}`) - } + // 输出详细错误信息,便于排查 + if (isHttpError(error)) { + console.error(`[RouteGuard] 错误码: ${error.code}, 消息: ${error.message}`) + } - // 跳转到 500 页面,使用 replace 避免产生历史记录 - next({ name: 'Exception500', replace: true }) - } + // 跳转到 500 页面,使用 replace 避免产生历史记录 + next({ name: 'Exception500', replace: true }) + } } /** * 获取用户信息 */ async function fetchUserInfo(): Promise { - const userStore = useUserStore() - const data = await fetchGetUserInfo() - userStore.setUserInfo(data) - // 检查并清理工作台标签页(如果是不同用户登录) - userStore.checkAndClearWorktabs() + const userStore = useUserStore() + const data = await fetchGetUserInfo() + userStore.setUserInfo(data) + // 检查并清理工作台标签页(如果是不同用户登录) + userStore.checkAndClearWorktabs() } /** * 重置路由相关状态 */ export function resetRouterState(delay: number): void { - setTimeout(() => { - routeRegistry?.unregister() - IframeRouteManager.getInstance().clear() + setTimeout(() => { + routeRegistry?.unregister() + IframeRouteManager.getInstance().clear() - const menuStore = useMenuStore() - menuStore.removeAllDynamicRoutes() - menuStore.setMenuList([]) + const menuStore = useMenuStore() + menuStore.removeAllDynamicRoutes() + menuStore.setMenuList([]) - // 重置路由初始化状态,允许重新登录后再次初始化 - resetRouteInitState() - }, delay) + // 重置路由初始化状态,允许重新登录后再次初始化 + resetRouteInitState() + }, delay) } /** @@ -379,22 +379,22 @@ export function resetRouterState(delay: number): void { * @returns true 表示已处理跳转,false 表示无需跳转 */ function handleRootPathRedirect(to: RouteLocationNormalized, next: NavigationGuardNext): boolean { - if (to.path !== '/') { - return false - } + if (to.path !== '/') { + return false + } - const { homePath } = useCommon() - if (homePath.value && homePath.value !== '/') { - next({ path: homePath.value, replace: true }) - return true - } + const { homePath } = useCommon() + if (homePath.value && homePath.value !== '/') { + next({ path: homePath.value, replace: true }) + return true + } - return false + return false } /** * 判断是否为未授权错误(401) */ function isUnauthorizedError(error: unknown): boolean { - return isHttpError(error) && error.code === ApiStatus.unauthorized + return isHttpError(error) && error.code === ApiStatus.unauthorized } diff --git a/src/router/index.ts b/src/router/index.ts index 286ae58..bd9c5ee 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -7,16 +7,16 @@ import { setupAfterEachGuard } from './guards/afterEach' // 创建路由实例 export const router = createRouter({ - history: createWebHashHistory(), - routes: staticRoutes // 静态路由 + history: createWebHashHistory(), + routes: staticRoutes // 静态路由 }) // 初始化路由 export function initRouter(app: App): void { - configureNProgress() // 顶部进度条 - setupBeforeEachGuard(router) // 路由前置守卫 - setupAfterEachGuard(router) // 路由后置守卫 - app.use(router) + configureNProgress() // 顶部进度条 + setupBeforeEachGuard(router) // 路由前置守卫 + setupAfterEachGuard(router) // 路由后置守卫 + app.use(router) } // 主页路径,默认使用菜单第一个有效路径,配置后使用此路径 diff --git a/src/router/modules/dashboard.ts b/src/router/modules/dashboard.ts index 5f9c3e9..c7036db 100644 --- a/src/router/modules/dashboard.ts +++ b/src/router/modules/dashboard.ts @@ -1,24 +1,24 @@ import { AppRouteRecord } from '@/types/router' export const dashboardRoutes: AppRouteRecord = { - name: 'Dashboard', - path: '/dashboard', - component: '/index/index', - meta: { - title: 'menus.dashboard.title', - icon: 'ri:pie-chart-line', - roles: ['R_SUPER', 'R_ADMIN'] - }, - children: [ - { - path: 'console', - name: 'Console', - component: '/dashboard/console', - meta: { - title: 'menus.dashboard.console', - keepAlive: false, - fixedTab: true - } - } - ] + name: 'Dashboard', + path: '/dashboard', + component: '/index/index', + meta: { + title: 'menus.dashboard.title', + icon: 'ri:pie-chart-line', + roles: ['R_SUPER', 'R_ADMIN'] + }, + children: [ + { + path: 'console', + name: 'Console', + component: '/dashboard/console', + meta: { + title: 'menus.dashboard.console', + keepAlive: false, + fixedTab: true + } + } + ] } diff --git a/src/router/modules/exception.ts b/src/router/modules/exception.ts index 07c5604..8aeea59 100644 --- a/src/router/modules/exception.ts +++ b/src/router/modules/exception.ts @@ -1,46 +1,46 @@ import { AppRouteRecord } from '@/types/router' export const exceptionRoutes: AppRouteRecord = { - path: '/exception', - name: 'Exception', - component: '/index/index', - meta: { - title: 'menus.exception.title', - icon: 'ri:error-warning-line' - }, - children: [ - { - path: '403', - name: 'Exception403', - component: '/exception/403', - meta: { - title: 'menus.exception.forbidden', - keepAlive: true, - isHideTab: true, - isFullPage: true - } - }, - { - path: '404', - name: 'Exception404', - component: '/exception/404', - meta: { - title: 'menus.exception.notFound', - keepAlive: true, - isHideTab: true, - isFullPage: true - } - }, - { - path: '500', - name: 'Exception500', - component: '/exception/500', - meta: { - title: 'menus.exception.serverError', - keepAlive: true, - isHideTab: true, - isFullPage: true - } - } - ] + path: '/exception', + name: 'Exception', + component: '/index/index', + meta: { + title: 'menus.exception.title', + icon: 'ri:error-warning-line' + }, + children: [ + { + path: '403', + name: 'Exception403', + component: '/exception/403', + meta: { + title: 'menus.exception.forbidden', + keepAlive: true, + isHideTab: true, + isFullPage: true + } + }, + { + path: '404', + name: 'Exception404', + component: '/exception/404', + meta: { + title: 'menus.exception.notFound', + keepAlive: true, + isHideTab: true, + isFullPage: true + } + }, + { + path: '500', + name: 'Exception500', + component: '/exception/500', + meta: { + title: 'menus.exception.serverError', + keepAlive: true, + isHideTab: true, + isFullPage: true + } + } + ] } diff --git a/src/router/modules/index.ts b/src/router/modules/index.ts index deff162..f0bc8df 100644 --- a/src/router/modules/index.ts +++ b/src/router/modules/index.ts @@ -8,8 +8,8 @@ import { exceptionRoutes } from './exception' * 导出所有模块化路由 */ export const routeModules: AppRouteRecord[] = [ - dashboardRoutes, - systemRoutes, - resultRoutes, - exceptionRoutes + dashboardRoutes, + systemRoutes, + resultRoutes, + exceptionRoutes ] diff --git a/src/router/modules/result.ts b/src/router/modules/result.ts index 575a2f7..30a328d 100644 --- a/src/router/modules/result.ts +++ b/src/router/modules/result.ts @@ -1,33 +1,33 @@ import { AppRouteRecord } from '@/types/router' export const resultRoutes: AppRouteRecord = { - path: '/result', - name: 'Result', - component: '/index/index', - meta: { - title: 'menus.result.title', - icon: 'ri:checkbox-circle-line' - }, - children: [ - { - path: 'success', - name: 'ResultSuccess', - component: '/result/success', - meta: { - title: 'menus.result.success', - icon: 'ri:checkbox-circle-line', - keepAlive: true - } - }, - { - path: 'fail', - name: 'ResultFail', - component: '/result/fail', - meta: { - title: 'menus.result.fail', - icon: 'ri:close-circle-line', - keepAlive: true - } - } - ] + path: '/result', + name: 'Result', + component: '/index/index', + meta: { + title: 'menus.result.title', + icon: 'ri:checkbox-circle-line' + }, + children: [ + { + path: 'success', + name: 'ResultSuccess', + component: '/result/success', + meta: { + title: 'menus.result.success', + icon: 'ri:checkbox-circle-line', + keepAlive: true + } + }, + { + path: 'fail', + name: 'ResultFail', + component: '/result/fail', + meta: { + title: 'menus.result.fail', + icon: 'ri:close-circle-line', + keepAlive: true + } + } + ] } diff --git a/src/router/modules/system.ts b/src/router/modules/system.ts index 16df585..be432b9 100644 --- a/src/router/modules/system.ts +++ b/src/router/modules/system.ts @@ -1,60 +1,60 @@ import { AppRouteRecord } from '@/types/router' export const systemRoutes: AppRouteRecord = { - path: '/system', - name: 'System', - component: '/index/index', - meta: { - title: 'menus.system.title', - icon: 'ri:user-3-line', - roles: ['R_SUPER', 'R_ADMIN'] - }, - children: [ - { - path: 'user', - name: 'User', - component: '/system/user', - meta: { - title: 'menus.system.user', - keepAlive: true, - roles: ['R_SUPER', 'R_ADMIN'] - } - }, - { - path: 'role', - name: 'Role', - component: '/system/role', - meta: { - title: 'menus.system.role', - keepAlive: true, - roles: ['R_SUPER'] - } - }, - { - path: 'user-center', - name: 'UserCenter', - component: '/system/user-center', - meta: { - title: 'menus.system.userCenter', - isHide: true, - keepAlive: true, - isHideTab: true - } - }, - { - path: 'menu', - name: 'Menus', - component: '/system/menu', - meta: { - title: 'menus.system.menu', - keepAlive: true, - roles: ['R_SUPER'], - authList: [ - { title: '新增', authMark: 'add' }, - { title: '编辑', authMark: 'edit' }, - { title: '删除', authMark: 'delete' } - ] - } - } - ] + path: '/system', + name: 'System', + component: '/index/index', + meta: { + title: 'menus.system.title', + icon: 'ri:user-3-line', + roles: ['R_SUPER', 'R_ADMIN'] + }, + children: [ + { + path: 'user', + name: 'User', + component: '/system/user', + meta: { + title: 'menus.system.user', + keepAlive: true, + roles: ['R_SUPER', 'R_ADMIN'] + } + }, + { + path: 'role', + name: 'Role', + component: '/system/role', + meta: { + title: 'menus.system.role', + keepAlive: true, + roles: ['R_SUPER'] + } + }, + { + path: 'user-center', + name: 'UserCenter', + component: '/system/user-center', + meta: { + title: 'menus.system.userCenter', + isHide: true, + keepAlive: true, + isHideTab: true + } + }, + { + path: 'menu', + name: 'Menus', + component: '/system/menu', + meta: { + title: 'menus.system.menu', + keepAlive: true, + roles: ['R_SUPER'], + authList: [ + { title: '新增', authMark: 'add' }, + { title: '编辑', authMark: 'edit' }, + { title: '删除', authMark: 'delete' } + ] + } + } + ] } diff --git a/src/router/routes/staticRoutes.ts b/src/router/routes/staticRoutes.ts index 334d0c2..f683034 100644 --- a/src/router/routes/staticRoutes.ts +++ b/src/router/routes/staticRoutes.ts @@ -11,62 +11,62 @@ import { AppRouteRecordRaw } from '@/utils/router' * 2、静态路由不管是否登录都可以访问 */ export const staticRoutes: AppRouteRecordRaw[] = [ - // 不需要登录就能访问的路由示例 - // { - // path: '/welcome', - // name: 'WelcomeStatic', - // component: () => import('@views/dashboard/console/index.vue'), - // meta: { title: 'menus.dashboard.title' } - // }, - { - path: '/auth/login', - name: 'Login', - component: () => import('@views/auth/login/index.vue'), - meta: { title: 'menus.login.title', isHideTab: true } - }, - { - path: '/auth/register', - name: 'Register', - component: () => import('@views/auth/register/index.vue'), - meta: { title: 'menus.register.title', isHideTab: true } - }, - { - path: '/auth/forget-password', - name: 'ForgetPassword', - component: () => import('@views/auth/forget-password/index.vue'), - meta: { title: 'menus.forgetPassword.title', isHideTab: true } - }, - { - path: '/403', - name: 'Exception403', - component: () => import('@views/exception/403/index.vue'), - meta: { title: '403', isHideTab: true } - }, - { - path: '/:pathMatch(.*)*', - name: 'Exception404', - component: () => import('@views/exception/404/index.vue'), - meta: { title: '404', isHideTab: true } - }, - { - path: '/500', - name: 'Exception500', - component: () => import('@views/exception/500/index.vue'), - meta: { title: '500', isHideTab: true } - }, - { - path: '/outside', - component: () => import('@views/index/index.vue'), - name: 'Outside', - meta: { title: 'menus.outside.title' }, - children: [ - // iframe 内嵌页面 - { - path: '/outside/iframe/:path', - name: 'Iframe', - component: () => import('@/views/outside/Iframe.vue'), - meta: { title: 'iframe' } - } - ] - } + // 不需要登录就能访问的路由示例 + // { + // path: '/welcome', + // name: 'WelcomeStatic', + // component: () => import('@views/dashboard/console/index.vue'), + // meta: { title: 'menus.dashboard.title' } + // }, + { + path: '/auth/login', + name: 'Login', + component: () => import('@views/auth/login/index.vue'), + meta: { title: 'menus.login.title', isHideTab: true } + }, + { + path: '/auth/register', + name: 'Register', + component: () => import('@views/auth/register/index.vue'), + meta: { title: 'menus.register.title', isHideTab: true } + }, + { + path: '/auth/forget-password', + name: 'ForgetPassword', + component: () => import('@views/auth/forget-password/index.vue'), + meta: { title: 'menus.forgetPassword.title', isHideTab: true } + }, + { + path: '/403', + name: 'Exception403', + component: () => import('@views/exception/403/index.vue'), + meta: { title: '403', isHideTab: true } + }, + { + path: '/:pathMatch(.*)*', + name: 'Exception404', + component: () => import('@views/exception/404/index.vue'), + meta: { title: '404', isHideTab: true } + }, + { + path: '/500', + name: 'Exception500', + component: () => import('@views/exception/500/index.vue'), + meta: { title: '500', isHideTab: true } + }, + { + path: '/outside', + component: () => import('@views/index/index.vue'), + name: 'Outside', + meta: { title: 'menus.outside.title' }, + children: [ + // iframe 内嵌页面 + { + path: '/outside/iframe/:path', + name: 'Iframe', + component: () => import('@/views/outside/Iframe.vue'), + meta: { title: 'iframe' } + } + ] + } ] diff --git a/src/router/routesAlias.ts b/src/router/routesAlias.ts index 2af1c68..a14bb7c 100644 --- a/src/router/routesAlias.ts +++ b/src/router/routesAlias.ts @@ -3,6 +3,6 @@ # 存放系统级公共路由路径,如布局容器、登录页等 */ export enum RoutesAlias { - Layout = '/index/index', // 布局容器 - Login = '/auth/login' // 登录页 + Layout = '/index/index', // 布局容器 + Login = '/auth/login' // 登录页 } diff --git a/src/store/index.ts b/src/store/index.ts index b485999..bbd5fa5 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -34,19 +34,19 @@ const storageKeyManager = new StorageKeyManager() // 配置持久化插件 store.use( - createPersistedState({ - key: (storeId: string) => storageKeyManager.getStorageKey(storeId), - storage: localStorage, - serializer: { - serialize: JSON.stringify, - deserialize: JSON.parse - } - }) + createPersistedState({ + key: (storeId: string) => storageKeyManager.getStorageKey(storeId), + storage: localStorage, + serializer: { + serialize: JSON.stringify, + deserialize: JSON.parse + } + }) ) /** * 初始化 Store */ export function initStore(app: App): void { - app.use(store) + app.use(store) } diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts index 85d13da..dbbc0b7 100644 --- a/src/store/modules/menu.ts +++ b/src/store/modules/menu.ts @@ -39,71 +39,71 @@ import { HOME_PAGE_PATH } from '@/router' * 管理应用的菜单列表、首页路径、菜单宽度和动态路由移除函数 */ export const useMenuStore = defineStore('menuStore', () => { - /** 首页路径 */ - const homePath = ref(HOME_PAGE_PATH) - /** 菜单列表 */ - const menuList = ref([]) - /** 菜单宽度 */ - const menuWidth = ref('') - /** 存储路由移除函数的数组 */ - const removeRouteFns = ref<(() => void)[]>([]) + /** 首页路径 */ + const homePath = ref(HOME_PAGE_PATH) + /** 菜单列表 */ + const menuList = ref([]) + /** 菜单宽度 */ + const menuWidth = ref('') + /** 存储路由移除函数的数组 */ + const removeRouteFns = ref<(() => void)[]>([]) - /** - * 设置菜单列表 - * @param list 菜单路由记录数组 - */ - const setMenuList = (list: AppRouteRecord[]) => { - menuList.value = list - setHomePath(HOME_PAGE_PATH || getFirstMenuPath(list)) - } + /** + * 设置菜单列表 + * @param list 菜单路由记录数组 + */ + const setMenuList = (list: AppRouteRecord[]) => { + menuList.value = list + setHomePath(HOME_PAGE_PATH || getFirstMenuPath(list)) + } - /** - * 获取首页路径 - * @returns 首页路径字符串 - */ - const getHomePath = () => homePath.value + /** + * 获取首页路径 + * @returns 首页路径字符串 + */ + const getHomePath = () => homePath.value - /** - * 设置主页路径 - * @param path 主页路径 - */ - const setHomePath = (path: string) => { - homePath.value = path - } + /** + * 设置主页路径 + * @param path 主页路径 + */ + const setHomePath = (path: string) => { + homePath.value = path + } - /** - * 添加路由移除函数 - * @param fns 要添加的路由移除函数数组 - */ - const addRemoveRouteFns = (fns: (() => void)[]) => { - removeRouteFns.value.push(...fns) - } + /** + * 添加路由移除函数 + * @param fns 要添加的路由移除函数数组 + */ + const addRemoveRouteFns = (fns: (() => void)[]) => { + removeRouteFns.value.push(...fns) + } - /** - * 移除所有动态路由 - * 执行所有存储的路由移除函数并清空数组 - */ - const removeAllDynamicRoutes = () => { - removeRouteFns.value.forEach((fn) => fn()) - removeRouteFns.value = [] - } + /** + * 移除所有动态路由 + * 执行所有存储的路由移除函数并清空数组 + */ + const removeAllDynamicRoutes = () => { + removeRouteFns.value.forEach((fn) => fn()) + removeRouteFns.value = [] + } - /** - * 清空路由移除函数数组 - */ - const clearRemoveRouteFns = () => { - removeRouteFns.value = [] - } + /** + * 清空路由移除函数数组 + */ + const clearRemoveRouteFns = () => { + removeRouteFns.value = [] + } - return { - menuList, - menuWidth, - removeRouteFns, - setMenuList, - getHomePath, - setHomePath, - addRemoveRouteFns, - removeAllDynamicRoutes, - clearRemoveRouteFns - } + return { + menuList, + menuWidth, + removeRouteFns, + setMenuList, + getHomePath, + setHomePath, + addRemoveRouteFns, + removeAllDynamicRoutes, + clearRemoveRouteFns + } }) diff --git a/src/store/modules/setting.ts b/src/store/modules/setting.ts index 2878259..bdb97b2 100644 --- a/src/store/modules/setting.ts +++ b/src/store/modules/setting.ts @@ -45,406 +45,408 @@ import { SETTING_DEFAULT_CONFIG } from '@/config/setting' * 管理应用的菜单、主题、界面显示等各项设置 */ export const useSettingStore = defineStore( - 'settingStore', - () => { - // 菜单相关设置 - /** 菜单类型 */ - const menuType = ref(SETTING_DEFAULT_CONFIG.menuType) - /** 菜单展开宽度 */ - const menuOpenWidth = ref(SETTING_DEFAULT_CONFIG.menuOpenWidth) - /** 菜单是否展开 */ - const menuOpen = ref(SETTING_DEFAULT_CONFIG.menuOpen) - /** 双菜单是否显示文本 */ - const dualMenuShowText = ref(SETTING_DEFAULT_CONFIG.dualMenuShowText) + 'settingStore', + () => { + // 菜单相关设置 + /** 菜单类型 */ + const menuType = ref(SETTING_DEFAULT_CONFIG.menuType) + /** 菜单展开宽度 */ + const menuOpenWidth = ref(SETTING_DEFAULT_CONFIG.menuOpenWidth) + /** 菜单是否展开 */ + const menuOpen = ref(SETTING_DEFAULT_CONFIG.menuOpen) + /** 双菜单是否显示文本 */ + const dualMenuShowText = ref(SETTING_DEFAULT_CONFIG.dualMenuShowText) - // 主题相关设置 - /** 系统主题类型 */ - const systemThemeType = ref(SETTING_DEFAULT_CONFIG.systemThemeType) - /** 系统主题模式 */ - const systemThemeMode = ref(SETTING_DEFAULT_CONFIG.systemThemeMode) - /** 菜单主题类型 */ - const menuThemeType = ref(SETTING_DEFAULT_CONFIG.menuThemeType) - /** 系统主题颜色 */ - const systemThemeColor = ref(SETTING_DEFAULT_CONFIG.systemThemeColor) + // 主题相关设置 + /** 系统主题类型 */ + const systemThemeType = ref(SETTING_DEFAULT_CONFIG.systemThemeType) + /** 系统主题模式 */ + const systemThemeMode = ref(SETTING_DEFAULT_CONFIG.systemThemeMode) + /** 菜单主题类型 */ + const menuThemeType = ref(SETTING_DEFAULT_CONFIG.menuThemeType) + /** 系统主题颜色 */ + const systemThemeColor = ref(SETTING_DEFAULT_CONFIG.systemThemeColor) - // 界面显示设置 - /** 是否显示菜单按钮 */ - const showMenuButton = ref(SETTING_DEFAULT_CONFIG.showMenuButton) - /** 是否显示快速入口 */ - const showFastEnter = ref(SETTING_DEFAULT_CONFIG.showFastEnter) - /** 是否显示刷新按钮 */ - const showRefreshButton = ref(SETTING_DEFAULT_CONFIG.showRefreshButton) - /** 是否显示面包屑 */ - const showCrumbs = ref(SETTING_DEFAULT_CONFIG.showCrumbs) - /** 是否显示工作台标签 */ - const showWorkTab = ref(SETTING_DEFAULT_CONFIG.showWorkTab) - /** 是否显示语言切换 */ - const showLanguage = ref(SETTING_DEFAULT_CONFIG.showLanguage) - /** 是否显示进度条 */ - const showNprogress = ref(SETTING_DEFAULT_CONFIG.showNprogress) - /** 是否显示设置引导 */ - const showSettingGuide = ref(SETTING_DEFAULT_CONFIG.showSettingGuide) - /** 是否显示节日文本 */ - const showFestivalText = ref(SETTING_DEFAULT_CONFIG.showFestivalText) - /** 是否显示水印 */ - const watermarkVisible = ref(SETTING_DEFAULT_CONFIG.watermarkVisible) + // 界面显示设置 + /** 是否显示菜单按钮 */ + const showMenuButton = ref(SETTING_DEFAULT_CONFIG.showMenuButton) + /** 是否显示快速入口 */ + const showFastEnter = ref(SETTING_DEFAULT_CONFIG.showFastEnter) + /** 是否显示刷新按钮 */ + const showRefreshButton = ref(SETTING_DEFAULT_CONFIG.showRefreshButton) + /** 是否显示面包屑 */ + const showCrumbs = ref(SETTING_DEFAULT_CONFIG.showCrumbs) + /** 是否显示工作台标签 */ + const showWorkTab = ref(SETTING_DEFAULT_CONFIG.showWorkTab) + /** 是否显示语言切换 */ + const showLanguage = ref(SETTING_DEFAULT_CONFIG.showLanguage) + /** 是否显示进度条 */ + const showNprogress = ref(SETTING_DEFAULT_CONFIG.showNprogress) + /** 是否显示设置引导 */ + const showSettingGuide = ref(SETTING_DEFAULT_CONFIG.showSettingGuide) + /** 是否显示节日文本 */ + const showFestivalText = ref(SETTING_DEFAULT_CONFIG.showFestivalText) + /** 是否显示水印 */ + const watermarkVisible = ref(SETTING_DEFAULT_CONFIG.watermarkVisible) - // 功能设置 - /** 是否自动关闭 */ - const autoClose = ref(SETTING_DEFAULT_CONFIG.autoClose) - /** 是否唯一展开 */ - const uniqueOpened = ref(SETTING_DEFAULT_CONFIG.uniqueOpened) - /** 是否色弱模式 */ - const colorWeak = ref(SETTING_DEFAULT_CONFIG.colorWeak) - /** 是否刷新 */ - const refresh = ref(SETTING_DEFAULT_CONFIG.refresh) - /** 是否加载节日烟花 */ - const holidayFireworksLoaded = ref(SETTING_DEFAULT_CONFIG.holidayFireworksLoaded) + // 功能设置 + /** 是否自动关闭 */ + const autoClose = ref(SETTING_DEFAULT_CONFIG.autoClose) + /** 是否唯一展开 */ + const uniqueOpened = ref(SETTING_DEFAULT_CONFIG.uniqueOpened) + /** 是否色弱模式 */ + const colorWeak = ref(SETTING_DEFAULT_CONFIG.colorWeak) + /** 是否刷新 */ + const refresh = ref(SETTING_DEFAULT_CONFIG.refresh) + /** 是否加载节日烟花 */ + const holidayFireworksLoaded = ref(SETTING_DEFAULT_CONFIG.holidayFireworksLoaded) - // 样式设置 - /** 边框模式 */ - const boxBorderMode = ref(SETTING_DEFAULT_CONFIG.boxBorderMode) - /** 页面过渡效果 */ - const pageTransition = ref(SETTING_DEFAULT_CONFIG.pageTransition) - /** 标签页样式 */ - const tabStyle = ref(SETTING_DEFAULT_CONFIG.tabStyle) - /** 自定义圆角 */ - const customRadius = ref(SETTING_DEFAULT_CONFIG.customRadius) - /** 容器宽度 */ - const containerWidth = ref(SETTING_DEFAULT_CONFIG.containerWidth) + // 样式设置 + /** 边框模式 */ + const boxBorderMode = ref(SETTING_DEFAULT_CONFIG.boxBorderMode) + /** 页面过渡效果 */ + const pageTransition = ref(SETTING_DEFAULT_CONFIG.pageTransition) + /** 标签页样式 */ + const tabStyle = ref(SETTING_DEFAULT_CONFIG.tabStyle) + /** 自定义圆角 */ + const customRadius = ref(SETTING_DEFAULT_CONFIG.customRadius) + /** 容器宽度 */ + const containerWidth = ref(SETTING_DEFAULT_CONFIG.containerWidth) - // 节日相关 - /** 节日日期 */ - const festivalDate = ref('') + // 节日相关 + /** 节日日期 */ + const festivalDate = ref('') - /** - * 获取菜单主题 - * 根据当前主题类型和暗色模式返回对应的主题配置 - */ - const getMenuTheme = computed((): MenuThemeType => { - const list = AppConfig.themeList.filter((item) => item.theme === menuThemeType.value) - if (isDark.value) { - return AppConfig.darkMenuStyles[0] - } else { - return list[0] - } - }) + /** + * 获取菜单主题 + * 根据当前主题类型和暗色模式返回对应的主题配置 + */ + const getMenuTheme = computed((): MenuThemeType => { + const list = AppConfig.themeList.filter((item) => item.theme === menuThemeType.value) + if (isDark.value) { + return AppConfig.darkMenuStyles[0] + } else { + return list[0] + } + }) - /** - * 判断是否为暗色模式 - */ - const isDark = computed((): boolean => { - return systemThemeType.value === SystemThemeEnum.DARK - }) + /** + * 判断是否为暗色模式 + */ + const isDark = computed((): boolean => { + return systemThemeType.value === SystemThemeEnum.DARK + }) - /** - * 获取菜单展开宽度 - */ - const getMenuOpenWidth = computed((): string => { - return menuOpenWidth.value + 'px' || SETTING_DEFAULT_CONFIG.menuOpenWidth + 'px' - }) + /** + * 获取菜单展开宽度 + */ + const getMenuOpenWidth = computed((): string => { + return menuOpenWidth.value + 'px' || SETTING_DEFAULT_CONFIG.menuOpenWidth + 'px' + }) - /** - * 获取自定义圆角 - */ - const getCustomRadius = computed((): string => { - return customRadius.value + 'rem' || SETTING_DEFAULT_CONFIG.customRadius + 'rem' - }) + /** + * 获取自定义圆角 + */ + const getCustomRadius = computed((): string => { + return customRadius.value + 'rem' || SETTING_DEFAULT_CONFIG.customRadius + 'rem' + }) - /** - * 是否显示烟花 - * 根据当前日期和节日日期判断是否显示烟花效果 - */ - const isShowFireworks = computed((): boolean => { - return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true - }) + /** + * 是否显示烟花 + * 根据当前日期和节日日期判断是否显示烟花效果 + */ + const isShowFireworks = computed((): boolean => { + return festivalDate.value === useCeremony().currentFestivalData.value?.date + ? false + : true + }) - /** - * 切换菜单布局 - * @param type 菜单类型 - */ - const switchMenuLayouts = (type: MenuTypeEnum) => { - menuType.value = type - } + /** + * 切换菜单布局 + * @param type 菜单类型 + */ + const switchMenuLayouts = (type: MenuTypeEnum) => { + menuType.value = type + } - /** - * 设置菜单展开宽度 - * @param width 宽度值 - */ - const setMenuOpenWidth = (width: number) => { - menuOpenWidth.value = width - } + /** + * 设置菜单展开宽度 + * @param width 宽度值 + */ + const setMenuOpenWidth = (width: number) => { + menuOpenWidth.value = width + } - /** - * 设置全局主题 - * @param theme 主题类型 - * @param themeMode 主题模式 - */ - const setGlopTheme = (theme: SystemThemeEnum, themeMode: SystemThemeEnum) => { - systemThemeType.value = theme - systemThemeMode.value = themeMode - localStorage.setItem(StorageConfig.THEME_KEY, theme) - } + /** + * 设置全局主题 + * @param theme 主题类型 + * @param themeMode 主题模式 + */ + const setGlopTheme = (theme: SystemThemeEnum, themeMode: SystemThemeEnum) => { + systemThemeType.value = theme + systemThemeMode.value = themeMode + localStorage.setItem(StorageConfig.THEME_KEY, theme) + } - /** - * 切换菜单样式 - * @param theme 菜单主题 - */ - const switchMenuStyles = (theme: MenuThemeEnum) => { - menuThemeType.value = theme - } + /** + * 切换菜单样式 + * @param theme 菜单主题 + */ + const switchMenuStyles = (theme: MenuThemeEnum) => { + menuThemeType.value = theme + } - /** - * 设置Element Plus主题颜色 - * @param theme 主题颜色 - */ - const setElementTheme = (theme: string) => { - systemThemeColor.value = theme - setElementThemeColor(theme) - } + /** + * 设置Element Plus主题颜色 + * @param theme 主题颜色 + */ + const setElementTheme = (theme: string) => { + systemThemeColor.value = theme + setElementThemeColor(theme) + } - /** - * 切换边框模式 - */ - const setBorderMode = () => { - boxBorderMode.value = !boxBorderMode.value - } + /** + * 切换边框模式 + */ + const setBorderMode = () => { + boxBorderMode.value = !boxBorderMode.value + } - /** - * 设置容器宽度 - * @param width 容器宽度枚举值 - */ - const setContainerWidth = (width: ContainerWidthEnum) => { - containerWidth.value = width - } + /** + * 设置容器宽度 + * @param width 容器宽度枚举值 + */ + const setContainerWidth = (width: ContainerWidthEnum) => { + containerWidth.value = width + } - /** - * 切换唯一展开模式 - */ - const setUniqueOpened = () => { - uniqueOpened.value = !uniqueOpened.value - } + /** + * 切换唯一展开模式 + */ + const setUniqueOpened = () => { + uniqueOpened.value = !uniqueOpened.value + } - /** - * 切换菜单按钮显示 - */ - const setButton = () => { - showMenuButton.value = !showMenuButton.value - } + /** + * 切换菜单按钮显示 + */ + const setButton = () => { + showMenuButton.value = !showMenuButton.value + } - /** - * 切换快速入口显示 - */ - const setFastEnter = () => { - showFastEnter.value = !showFastEnter.value - } + /** + * 切换快速入口显示 + */ + const setFastEnter = () => { + showFastEnter.value = !showFastEnter.value + } - /** - * 切换自动关闭 - */ - const setAutoClose = () => { - autoClose.value = !autoClose.value - } + /** + * 切换自动关闭 + */ + const setAutoClose = () => { + autoClose.value = !autoClose.value + } - /** - * 切换刷新按钮显示 - */ - const setShowRefreshButton = () => { - showRefreshButton.value = !showRefreshButton.value - } + /** + * 切换刷新按钮显示 + */ + const setShowRefreshButton = () => { + showRefreshButton.value = !showRefreshButton.value + } - /** - * 切换面包屑显示 - */ - const setCrumbs = () => { - showCrumbs.value = !showCrumbs.value - } + /** + * 切换面包屑显示 + */ + const setCrumbs = () => { + showCrumbs.value = !showCrumbs.value + } - /** - * 设置工作台标签显示 - * @param show 是否显示 - */ - const setWorkTab = (show: boolean) => { - showWorkTab.value = show - } + /** + * 设置工作台标签显示 + * @param show 是否显示 + */ + const setWorkTab = (show: boolean) => { + showWorkTab.value = show + } - /** - * 切换语言切换显示 - */ - const setLanguage = () => { - showLanguage.value = !showLanguage.value - } + /** + * 切换语言切换显示 + */ + const setLanguage = () => { + showLanguage.value = !showLanguage.value + } - /** - * 切换进度条显示 - */ - const setNprogress = () => { - showNprogress.value = !showNprogress.value - } + /** + * 切换进度条显示 + */ + const setNprogress = () => { + showNprogress.value = !showNprogress.value + } - /** - * 切换色弱模式 - */ - const setColorWeak = () => { - colorWeak.value = !colorWeak.value - } + /** + * 切换色弱模式 + */ + const setColorWeak = () => { + colorWeak.value = !colorWeak.value + } - /** - * 隐藏设置引导 - */ - const hideSettingGuide = () => { - showSettingGuide.value = false - } + /** + * 隐藏设置引导 + */ + const hideSettingGuide = () => { + showSettingGuide.value = false + } - /** - * 显示设置引导 - */ - const openSettingGuide = () => { - showSettingGuide.value = true - } + /** + * 显示设置引导 + */ + const openSettingGuide = () => { + showSettingGuide.value = true + } - /** - * 设置页面过渡效果 - * @param transition 过渡效果名称 - */ - const setPageTransition = (transition: string) => { - pageTransition.value = transition - } + /** + * 设置页面过渡效果 + * @param transition 过渡效果名称 + */ + const setPageTransition = (transition: string) => { + pageTransition.value = transition + } - /** - * 设置标签页样式 - * @param style 样式名称 - */ - const setTabStyle = (style: string) => { - tabStyle.value = style - } + /** + * 设置标签页样式 + * @param style 样式名称 + */ + const setTabStyle = (style: string) => { + tabStyle.value = style + } - /** - * 设置菜单展开状态 - * @param open 是否展开 - */ - const setMenuOpen = (open: boolean) => { - menuOpen.value = open - } + /** + * 设置菜单展开状态 + * @param open 是否展开 + */ + const setMenuOpen = (open: boolean) => { + menuOpen.value = open + } - /** - * 刷新页面 - */ - const reload = () => { - refresh.value = !refresh.value - } + /** + * 刷新页面 + */ + const reload = () => { + refresh.value = !refresh.value + } - /** - * 设置水印显示 - * @param visible 是否显示 - */ - const setWatermarkVisible = (visible: boolean) => { - watermarkVisible.value = visible - } + /** + * 设置水印显示 + * @param visible 是否显示 + */ + const setWatermarkVisible = (visible: boolean) => { + watermarkVisible.value = visible + } - /** - * 设置自定义圆角 - * @param radius 圆角值 - */ - const setCustomRadius = (radius: string) => { - customRadius.value = radius - document.documentElement.style.setProperty('--custom-radius', `${radius}rem`) - } + /** + * 设置自定义圆角 + * @param radius 圆角值 + */ + const setCustomRadius = (radius: string) => { + customRadius.value = radius + document.documentElement.style.setProperty('--custom-radius', `${radius}rem`) + } - /** - * 设置节日烟花加载状态 - * @param isLoad 是否已加载 - */ - const setholidayFireworksLoaded = (isLoad: boolean) => { - holidayFireworksLoaded.value = isLoad - } + /** + * 设置节日烟花加载状态 + * @param isLoad 是否已加载 + */ + const setholidayFireworksLoaded = (isLoad: boolean) => { + holidayFireworksLoaded.value = isLoad + } - /** - * 设置节日文本显示 - * @param show 是否显示 - */ - const setShowFestivalText = (show: boolean) => { - showFestivalText.value = show - } + /** + * 设置节日文本显示 + * @param show 是否显示 + */ + const setShowFestivalText = (show: boolean) => { + showFestivalText.value = show + } - const setFestivalDate = (date: string) => { - festivalDate.value = date - } + const setFestivalDate = (date: string) => { + festivalDate.value = date + } - const setDualMenuShowText = (show: boolean) => { - dualMenuShowText.value = show - } + const setDualMenuShowText = (show: boolean) => { + dualMenuShowText.value = show + } - return { - menuType, - menuOpenWidth, - systemThemeType, - systemThemeMode, - menuThemeType, - systemThemeColor, - boxBorderMode, - uniqueOpened, - showMenuButton, - showFastEnter, - showRefreshButton, - showCrumbs, - autoClose, - showWorkTab, - showLanguage, - showNprogress, - colorWeak, - showSettingGuide, - pageTransition, - tabStyle, - menuOpen, - refresh, - watermarkVisible, - customRadius, - holidayFireworksLoaded, - showFestivalText, - festivalDate, - dualMenuShowText, - containerWidth, - getMenuTheme, - isDark, - getMenuOpenWidth, - getCustomRadius, - isShowFireworks, - switchMenuLayouts, - setMenuOpenWidth, - setGlopTheme, - switchMenuStyles, - setElementTheme, - setBorderMode, - setContainerWidth, - setUniqueOpened, - setButton, - setFastEnter, - setAutoClose, - setShowRefreshButton, - setCrumbs, - setWorkTab, - setLanguage, - setNprogress, - setColorWeak, - hideSettingGuide, - openSettingGuide, - setPageTransition, - setTabStyle, - setMenuOpen, - reload, - setWatermarkVisible, - setCustomRadius, - setholidayFireworksLoaded, - setShowFestivalText, - setFestivalDate, - setDualMenuShowText - } - }, - { - persist: { - key: 'setting', - storage: localStorage - } - } + return { + menuType, + menuOpenWidth, + systemThemeType, + systemThemeMode, + menuThemeType, + systemThemeColor, + boxBorderMode, + uniqueOpened, + showMenuButton, + showFastEnter, + showRefreshButton, + showCrumbs, + autoClose, + showWorkTab, + showLanguage, + showNprogress, + colorWeak, + showSettingGuide, + pageTransition, + tabStyle, + menuOpen, + refresh, + watermarkVisible, + customRadius, + holidayFireworksLoaded, + showFestivalText, + festivalDate, + dualMenuShowText, + containerWidth, + getMenuTheme, + isDark, + getMenuOpenWidth, + getCustomRadius, + isShowFireworks, + switchMenuLayouts, + setMenuOpenWidth, + setGlopTheme, + switchMenuStyles, + setElementTheme, + setBorderMode, + setContainerWidth, + setUniqueOpened, + setButton, + setFastEnter, + setAutoClose, + setShowRefreshButton, + setCrumbs, + setWorkTab, + setLanguage, + setNprogress, + setColorWeak, + hideSettingGuide, + openSettingGuide, + setPageTransition, + setTabStyle, + setMenuOpen, + reload, + setWatermarkVisible, + setCustomRadius, + setholidayFireworksLoaded, + setShowFestivalText, + setFestivalDate, + setDualMenuShowText + } + }, + { + persist: { + key: 'setting', + storage: localStorage + } + } ) diff --git a/src/store/modules/table.ts b/src/store/modules/table.ts index 094c310..5d9d958 100644 --- a/src/store/modules/table.ts +++ b/src/store/modules/table.ts @@ -31,67 +31,67 @@ import { TableSizeEnum } from '@/enums/formEnum' // 表格 export const useTableStore = defineStore( - 'tableStore', - () => { - // 表格大小 - const tableSize = ref(TableSizeEnum.DEFAULT) - // 斑马纹 - const isZebra = ref(false) - // 边框 - const isBorder = ref(false) - // 表头背景 - const isHeaderBackground = ref(false) + 'tableStore', + () => { + // 表格大小 + const tableSize = ref(TableSizeEnum.DEFAULT) + // 斑马纹 + const isZebra = ref(false) + // 边框 + const isBorder = ref(false) + // 表头背景 + const isHeaderBackground = ref(false) - // 是否全屏 - const isFullScreen = ref(false) + // 是否全屏 + const isFullScreen = ref(false) - /** - * 设置表格大小 - * @param size 表格大小枚举值 - */ - const setTableSize = (size: TableSizeEnum) => (tableSize.value = size) + /** + * 设置表格大小 + * @param size 表格大小枚举值 + */ + const setTableSize = (size: TableSizeEnum) => (tableSize.value = size) - /** - * 设置斑马纹显示状态 - * @param value 是否显示斑马纹 - */ - const setIsZebra = (value: boolean) => (isZebra.value = value) + /** + * 设置斑马纹显示状态 + * @param value 是否显示斑马纹 + */ + const setIsZebra = (value: boolean) => (isZebra.value = value) - /** - * 设置表格边框显示状态 - * @param value 是否显示边框 - */ - const setIsBorder = (value: boolean) => (isBorder.value = value) + /** + * 设置表格边框显示状态 + * @param value 是否显示边框 + */ + const setIsBorder = (value: boolean) => (isBorder.value = value) - /** - * 设置表头背景显示状态 - * @param value 是否显示表头背景 - */ - const setIsHeaderBackground = (value: boolean) => (isHeaderBackground.value = value) + /** + * 设置表头背景显示状态 + * @param value 是否显示表头背景 + */ + const setIsHeaderBackground = (value: boolean) => (isHeaderBackground.value = value) - /** - * 设置是否全屏 - * @param value 是否全屏 - */ - const setIsFullScreen = (value: boolean) => (isFullScreen.value = value) + /** + * 设置是否全屏 + * @param value 是否全屏 + */ + const setIsFullScreen = (value: boolean) => (isFullScreen.value = value) - return { - tableSize, - isZebra, - isBorder, - isHeaderBackground, - setTableSize, - setIsZebra, - setIsBorder, - setIsHeaderBackground, - isFullScreen, - setIsFullScreen - } - }, - { - persist: { - key: 'table', - storage: localStorage - } - } + return { + tableSize, + isZebra, + isBorder, + isHeaderBackground, + setTableSize, + setIsZebra, + setIsBorder, + setIsHeaderBackground, + isFullScreen, + setIsFullScreen + } + }, + { + persist: { + key: 'table', + storage: localStorage + } + } ) diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 08f7684..a0b804a 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -48,188 +48,188 @@ import { StorageConfig } from '@/utils/storage/storage-config' * 管理用户登录状态、个人信息、语言设置、搜索历史、锁屏状态等 */ export const useUserStore = defineStore( - 'userStore', - () => { - // 语言设置 - const language = ref(LanguageEnum.ZH) - // 登录状态 - const isLogin = ref(false) - // 锁屏状态 - const isLock = ref(false) - // 锁屏密码 - const lockPassword = ref('') - // 用户信息 - const info = ref>({}) - // 搜索历史记录 - const searchHistory = ref([]) - // 访问令牌 - const accessToken = ref('') - // 刷新令牌 - const refreshToken = ref('') + 'userStore', + () => { + // 语言设置 + const language = ref(LanguageEnum.ZH) + // 登录状态 + const isLogin = ref(false) + // 锁屏状态 + const isLock = ref(false) + // 锁屏密码 + const lockPassword = ref('') + // 用户信息 + const info = ref>({}) + // 搜索历史记录 + const searchHistory = ref([]) + // 访问令牌 + const accessToken = ref('') + // 刷新令牌 + const refreshToken = ref('') - // 计算属性:获取用户信息 - const getUserInfo = computed(() => info.value) - // 计算属性:获取设置状态 - const getSettingState = computed(() => useSettingStore().$state) - // 计算属性:获取工作台状态 - const getWorktabState = computed(() => useWorktabStore().$state) + // 计算属性:获取用户信息 + const getUserInfo = computed(() => info.value) + // 计算属性:获取设置状态 + const getSettingState = computed(() => useSettingStore().$state) + // 计算属性:获取工作台状态 + const getWorktabState = computed(() => useWorktabStore().$state) - /** - * 设置用户信息 - * @param newInfo 新的用户信息 - */ - const setUserInfo = (newInfo: Api.Auth.UserInfo) => { - info.value = newInfo - } + /** + * 设置用户信息 + * @param newInfo 新的用户信息 + */ + const setUserInfo = (newInfo: Api.Auth.UserInfo) => { + info.value = newInfo + } - /** - * 设置登录状态 - * @param status 登录状态 - */ - const setLoginStatus = (status: boolean) => { - isLogin.value = status - } + /** + * 设置登录状态 + * @param status 登录状态 + */ + const setLoginStatus = (status: boolean) => { + isLogin.value = status + } - /** - * 设置语言 - * @param lang 语言枚举值 - */ - const setLanguage = (lang: LanguageEnum) => { - setPageTitle(router.currentRoute.value) - language.value = lang - } + /** + * 设置语言 + * @param lang 语言枚举值 + */ + const setLanguage = (lang: LanguageEnum) => { + setPageTitle(router.currentRoute.value) + language.value = lang + } - /** - * 设置搜索历史 - * @param list 搜索历史列表 - */ - const setSearchHistory = (list: AppRouteRecord[]) => { - searchHistory.value = list - } + /** + * 设置搜索历史 + * @param list 搜索历史列表 + */ + const setSearchHistory = (list: AppRouteRecord[]) => { + searchHistory.value = list + } - /** - * 设置锁屏状态 - * @param status 锁屏状态 - */ - const setLockStatus = (status: boolean) => { - isLock.value = status - } + /** + * 设置锁屏状态 + * @param status 锁屏状态 + */ + const setLockStatus = (status: boolean) => { + isLock.value = status + } - /** - * 设置锁屏密码 - * @param password 锁屏密码 - */ - const setLockPassword = (password: string) => { - lockPassword.value = password - } + /** + * 设置锁屏密码 + * @param password 锁屏密码 + */ + const setLockPassword = (password: string) => { + lockPassword.value = password + } - /** - * 设置令牌 - * @param newAccessToken 访问令牌 - * @param newRefreshToken 刷新令牌(可选) - */ - const setToken = (newAccessToken: string, newRefreshToken?: string) => { - accessToken.value = newAccessToken - if (newRefreshToken) { - refreshToken.value = newRefreshToken - } - } + /** + * 设置令牌 + * @param newAccessToken 访问令牌 + * @param newRefreshToken 刷新令牌(可选) + */ + const setToken = (newAccessToken: string, newRefreshToken?: string) => { + accessToken.value = newAccessToken + if (newRefreshToken) { + refreshToken.value = newRefreshToken + } + } - /** - * 退出登录 - * 清空所有用户相关状态并跳转到登录页 - * 如果是同一账号重新登录,保留工作台标签页 - */ - const logOut = () => { - // 保存当前用户 ID,用于下次登录时判断是否为同一用户 - const currentUserId = info.value.userId - if (currentUserId) { - localStorage.setItem(StorageConfig.LAST_USER_ID_KEY, String(currentUserId)) - } + /** + * 退出登录 + * 清空所有用户相关状态并跳转到登录页 + * 如果是同一账号重新登录,保留工作台标签页 + */ + const logOut = () => { + // 保存当前用户 ID,用于下次登录时判断是否为同一用户 + const currentUserId = info.value.userId + if (currentUserId) { + localStorage.setItem(StorageConfig.LAST_USER_ID_KEY, String(currentUserId)) + } - // 清空用户信息 - info.value = {} - // 重置登录状态 - isLogin.value = false - // 重置锁屏状态 - isLock.value = false - // 清空锁屏密码 - lockPassword.value = '' - // 清空访问令牌 - accessToken.value = '' - // 清空刷新令牌 - refreshToken.value = '' - // 注意:不清空工作台标签页,等下次登录时根据用户判断 - // 移除iframe路由缓存 - sessionStorage.removeItem('iframeRoutes') - // 清空主页路径 - useMenuStore().setHomePath('') - // 重置路由状态 - resetRouterState(500) - // 跳转到登录页,携带当前路由作为 redirect 参数 - const currentRoute = router.currentRoute.value - const redirect = currentRoute.path !== '/login' ? currentRoute.fullPath : undefined - router.push({ - name: 'Login', - query: redirect ? { redirect } : undefined - }) - } + // 清空用户信息 + info.value = {} + // 重置登录状态 + isLogin.value = false + // 重置锁屏状态 + isLock.value = false + // 清空锁屏密码 + lockPassword.value = '' + // 清空访问令牌 + accessToken.value = '' + // 清空刷新令牌 + refreshToken.value = '' + // 注意:不清空工作台标签页,等下次登录时根据用户判断 + // 移除iframe路由缓存 + sessionStorage.removeItem('iframeRoutes') + // 清空主页路径 + useMenuStore().setHomePath('') + // 重置路由状态 + resetRouterState(500) + // 跳转到登录页,携带当前路由作为 redirect 参数 + const currentRoute = router.currentRoute.value + const redirect = currentRoute.path !== '/login' ? currentRoute.fullPath : undefined + router.push({ + name: 'Login', + query: redirect ? { redirect } : undefined + }) + } - /** - * 检查并清理工作台标签页 - * 如果不是同一用户登录,清空工作台标签页 - * 应在登录成功后调用 - */ - const checkAndClearWorktabs = () => { - const lastUserId = localStorage.getItem(StorageConfig.LAST_USER_ID_KEY) - const currentUserId = info.value.userId + /** + * 检查并清理工作台标签页 + * 如果不是同一用户登录,清空工作台标签页 + * 应在登录成功后调用 + */ + const checkAndClearWorktabs = () => { + const lastUserId = localStorage.getItem(StorageConfig.LAST_USER_ID_KEY) + const currentUserId = info.value.userId - // 无法获取当前用户 ID,跳过检查 - if (!currentUserId) return + // 无法获取当前用户 ID,跳过检查 + if (!currentUserId) return - // 首次登录或缓存已清除,保留现有标签页 - if (!lastUserId) { - return - } + // 首次登录或缓存已清除,保留现有标签页 + if (!lastUserId) { + return + } - // 不同用户登录,清空工作台标签页 - if (String(currentUserId) !== lastUserId) { - const worktabStore = useWorktabStore() - worktabStore.opened = [] - worktabStore.keepAliveExclude = [] - } + // 不同用户登录,清空工作台标签页 + if (String(currentUserId) !== lastUserId) { + const worktabStore = useWorktabStore() + worktabStore.opened = [] + worktabStore.keepAliveExclude = [] + } - // 清除临时存储 - localStorage.removeItem(StorageConfig.LAST_USER_ID_KEY) - } + // 清除临时存储 + localStorage.removeItem(StorageConfig.LAST_USER_ID_KEY) + } - return { - language, - isLogin, - isLock, - lockPassword, - info, - searchHistory, - accessToken, - refreshToken, - getUserInfo, - getSettingState, - getWorktabState, - setUserInfo, - setLoginStatus, - setLanguage, - setSearchHistory, - setLockStatus, - setLockPassword, - setToken, - logOut, - checkAndClearWorktabs - } - }, - { - persist: { - key: 'user', - storage: localStorage - } - } + return { + language, + isLogin, + isLock, + lockPassword, + info, + searchHistory, + accessToken, + refreshToken, + getUserInfo, + getSettingState, + getWorktabState, + setUserInfo, + setLoginStatus, + setLanguage, + setSearchHistory, + setLockStatus, + setLockPassword, + setToken, + logOut, + checkAndClearWorktabs + } + }, + { + persist: { + key: 'user', + storage: localStorage + } + } ) diff --git a/src/store/modules/worktab.ts b/src/store/modules/worktab.ts index caa0d90..8c74858 100644 --- a/src/store/modules/worktab.ts +++ b/src/store/modules/worktab.ts @@ -45,524 +45,528 @@ import { WorkTab } from '@/types' import { useCommon } from '@/hooks/core/useCommon' interface WorktabState { - current: Partial - opened: WorkTab[] - keepAliveExclude: string[] + current: Partial + opened: WorkTab[] + keepAliveExclude: string[] } /** * 工作台标签页管理 Store */ export const useWorktabStore = defineStore( - 'worktabStore', - () => { - // 状态定义 - const current = ref>({}) - const opened = ref([]) - const keepAliveExclude = ref([]) - - // 计算属性 - const hasOpenedTabs = computed(() => opened.value.length > 0) - const hasMultipleTabs = computed(() => opened.value.length > 1) - const currentTabIndex = computed(() => - current.value.path ? opened.value.findIndex((tab) => tab.path === current.value.path) : -1 - ) - - /** - * 查找标签页索引 - */ - const findTabIndex = (path: string): number => { - return opened.value.findIndex((tab) => tab.path === path) - } - - /** - * 获取标签页 - */ - const getTab = (path: string): WorkTab | undefined => { - return opened.value.find((tab) => tab.path === path) - } - - /** - * 检查标签页是否可关闭 - */ - const isTabClosable = (tab: WorkTab): boolean => { - return !tab.fixedTab - } - - /** - * 安全的路由跳转 - */ - const safeRouterPush = (tab: Partial): void => { - if (!tab.path) { - console.warn('尝试跳转到无效路径的标签页') - return - } - - try { - router.push({ - path: tab.path, - query: tab.query as LocationQueryRaw - }) - } catch (error) { - console.error('路由跳转失败:', error) - } - } - - /** - * 打开或激活一个选项卡 - */ - const openTab = (tab: WorkTab): void => { - if (!tab.path) { - console.warn('尝试打开无效的标签页') - return - } - - // 从 keepAlive 排除列表中移除 - if (tab.name) { - removeKeepAliveExclude(tab.name) - } - - // 先根据路由名称查找(应对动态路由参数导致的多开问题),找不到再根据路径查找 - let existingIndex = -1 - if (tab.name) { - existingIndex = opened.value.findIndex((t) => t.name === tab.name) - } - if (existingIndex === -1) { - existingIndex = findTabIndex(tab.path) - } - - if (existingIndex === -1) { - // 新增标签页 - const insertIndex = tab.fixedTab ? findFixedTabInsertIndex() : opened.value.length - const newTab = { ...tab } - - if (tab.fixedTab) { - opened.value.splice(insertIndex, 0, newTab) - } else { - opened.value.push(newTab) - } - - current.value = newTab - } else { - // 更新现有标签页(当动态路由参数或查询变更时,复用同一标签) - const existingTab = opened.value[existingIndex] - - opened.value[existingIndex] = { - ...existingTab, - path: tab.path, - params: tab.params, - query: tab.query, - title: tab.title || existingTab.title, - fixedTab: tab.fixedTab ?? existingTab.fixedTab, - keepAlive: tab.keepAlive ?? existingTab.keepAlive, - name: tab.name || existingTab.name, - icon: tab.icon || existingTab.icon - } - - current.value = opened.value[existingIndex] - } - } - - /** - * 查找固定标签页的插入位置 - */ - const findFixedTabInsertIndex = (): number => { - let insertIndex = 0 - for (let i = 0; i < opened.value.length; i++) { - if (opened.value[i].fixedTab) { - insertIndex = i + 1 - } else { - break - } - } - return insertIndex - } - - /** - * 关闭指定的选项卡 - */ - const removeTab = (path: string): void => { - const targetTab = getTab(path) - const targetIndex = findTabIndex(path) - - if (targetIndex === -1) { - console.warn(`尝试关闭不存在的标签页: ${path}`) - return - } - - if (targetTab && !isTabClosable(targetTab)) { - console.warn(`尝试关闭固定标签页: ${path}`) - return - } - - // 从标签页列表中移除 - opened.value.splice(targetIndex, 1) - - // 处理缓存排除 - if (targetTab?.name) { - addKeepAliveExclude(targetTab) - } - - const { homePath } = useCommon() - - // 如果关闭后无标签页,跳转首页 - if (!hasOpenedTabs.value) { - if (path !== homePath.value) { - current.value = {} - safeRouterPush({ path: homePath.value }) - } - return - } - - // 如果关闭的是当前激活标签,需要激活其他标签 - if (current.value.path === path) { - const newIndex = targetIndex >= opened.value.length ? opened.value.length - 1 : targetIndex - current.value = opened.value[newIndex] - safeRouterPush(current.value) - } - } - - /** - * 关闭左侧选项卡 - */ - const removeLeft = (path: string): void => { - const targetIndex = findTabIndex(path) - - if (targetIndex === -1) { - console.warn(`尝试关闭左侧标签页,但目标标签页不存在: ${path}`) - return - } - - // 获取左侧可关闭的标签页 - const leftTabs = opened.value.slice(0, targetIndex) - const closableLeftTabs = leftTabs.filter(isTabClosable) - - if (closableLeftTabs.length === 0) { - console.warn('左侧没有可关闭的标签页') - return - } - - // 标记为缓存排除 - markTabsToRemove(closableLeftTabs) - - // 移除左侧可关闭的标签页 - opened.value = opened.value.filter( - (tab, index) => index >= targetIndex || !isTabClosable(tab) - ) - - // 确保当前标签是激活状态 - const targetTab = getTab(path) - if (targetTab) { - current.value = targetTab - } - } - - /** - * 关闭右侧选项卡 - */ - const removeRight = (path: string): void => { - const targetIndex = findTabIndex(path) - - if (targetIndex === -1) { - console.warn(`尝试关闭右侧标签页,但目标标签页不存在: ${path}`) - return - } - - // 获取右侧可关闭的标签页 - const rightTabs = opened.value.slice(targetIndex + 1) - const closableRightTabs = rightTabs.filter(isTabClosable) - - if (closableRightTabs.length === 0) { - console.warn('右侧没有可关闭的标签页') - return - } - - // 标记为缓存排除 - markTabsToRemove(closableRightTabs) - - // 移除右侧可关闭的标签页 - opened.value = opened.value.filter( - (tab, index) => index <= targetIndex || !isTabClosable(tab) - ) - - // 确保当前标签是激活状态 - const targetTab = getTab(path) - if (targetTab) { - current.value = targetTab - } - } - - /** - * 关闭其他选项卡 - */ - const removeOthers = (path: string): void => { - const targetTab = getTab(path) - - if (!targetTab) { - console.warn(`尝试关闭其他标签页,但目标标签页不存在: ${path}`) - return - } - - // 获取其他可关闭的标签页 - const otherTabs = opened.value.filter((tab) => tab.path !== path) - const closableTabs = otherTabs.filter(isTabClosable) - - if (closableTabs.length === 0) { - console.warn('没有其他可关闭的标签页') - return - } - - // 标记为缓存排除 - markTabsToRemove(closableTabs) - - // 只保留当前标签和固定标签 - opened.value = opened.value.filter((tab) => tab.path === path || !isTabClosable(tab)) - - // 确保当前标签是激活状态 - current.value = targetTab - } - - /** - * 关闭所有可关闭的标签页 - */ - const removeAll = (): void => { - const { homePath } = useCommon() - const hasFixedTabs = opened.value.some((tab) => tab.fixedTab) - - // 获取可关闭的标签页 - const closableTabs = opened.value.filter((tab) => { - if (!isTabClosable(tab)) return false - // 如果有固定标签,则所有可关闭的都可以关闭;否则保留首页 - return hasFixedTabs || tab.path !== homePath.value - }) - - if (closableTabs.length === 0) { - console.warn('没有可关闭的标签页') - return - } - - // 标记为缓存排除 - markTabsToRemove(closableTabs) - - // 保留不可关闭的标签页和首页(当没有固定标签时) - opened.value = opened.value.filter((tab) => { - return !isTabClosable(tab) || (!hasFixedTabs && tab.path === homePath.value) - }) - - // 处理激活状态 - if (!hasOpenedTabs.value) { - current.value = {} - safeRouterPush({ path: homePath.value }) - return - } - - // 选择激活的标签页:优先首页,其次第一个可用标签 - const homeTab = opened.value.find((tab) => tab.path === homePath.value) - const targetTab = homeTab || opened.value[0] - - current.value = targetTab - safeRouterPush(targetTab) - } - - /** - * 将指定选项卡添加到 keepAlive 排除列表中 - */ - const addKeepAliveExclude = (tab: WorkTab): void => { - if (!tab.keepAlive || !tab.name) return - - if (!keepAliveExclude.value.includes(tab.name)) { - keepAliveExclude.value.push(tab.name) - } - } - - /** - * 从 keepAlive 排除列表中移除指定组件名称 - */ - const removeKeepAliveExclude = (name: string): void => { - if (!name) return - - keepAliveExclude.value = keepAliveExclude.value.filter((item) => item !== name) - } - - /** - * 将传入的一组选项卡的组件名称标记为排除缓存 - */ - const markTabsToRemove = (tabs: WorkTab[]): void => { - tabs.forEach((tab) => { - if (tab.name) { - addKeepAliveExclude(tab) - } - }) - } - - /** - * 切换指定标签页的固定状态 - */ - const toggleFixedTab = (path: string): void => { - const targetIndex = findTabIndex(path) - - if (targetIndex === -1) { - console.warn(`尝试切换不存在标签页的固定状态: ${path}`) - return - } - - const tab = { ...opened.value[targetIndex] } - tab.fixedTab = !tab.fixedTab - - // 移除原位置 - opened.value.splice(targetIndex, 1) - - if (tab.fixedTab) { - // 固定标签插入到所有固定标签的末尾 - const firstNonFixedIndex = opened.value.findIndex((t) => !t.fixedTab) - const insertIndex = firstNonFixedIndex === -1 ? opened.value.length : firstNonFixedIndex - opened.value.splice(insertIndex, 0, tab) - } else { - // 非固定标签插入到所有固定标签后 - const fixedCount = opened.value.filter((t) => t.fixedTab).length - opened.value.splice(fixedCount, 0, tab) - } - - // 更新当前标签引用 - if (current.value.path === path) { - current.value = tab - } - } - - /** - * 验证工作台标签页的路由有效性 - */ - const validateWorktabs = (routerInstance: Router): void => { - try { - // 动态路由校验:优先使用路由 name 判断有效性;否则用 resolve 匹配参数化路径 - const isTabRouteValid = (tab: Partial): boolean => { - try { - if (tab.name) { - const routes = routerInstance.getRoutes() - if (routes.some((r) => r.name === tab.name)) return true - } - if (tab.path) { - const resolved = routerInstance.resolve({ - path: tab.path, - query: (tab.query as LocationQueryRaw) || undefined - }) - return resolved.matched.length > 0 - } - return false - } catch { - return false - } - } - - // 过滤出有效的标签页 - const validTabs = opened.value.filter((tab) => isTabRouteValid(tab)) - - if (validTabs.length !== opened.value.length) { - console.warn('发现无效的标签页路由,已自动清理') - opened.value = validTabs - } - - // 验证当前激活标签的有效性 - const isCurrentValid = current.value && isTabRouteValid(current.value) - - if (!isCurrentValid && validTabs.length > 0) { - console.warn('当前激活标签无效,已自动切换') - current.value = validTabs[0] - } else if (!isCurrentValid) { - current.value = {} - } - } catch (error) { - console.error('验证工作台标签页失败:', error) - } - } - - /** - * 清空所有状态(用于登出等场景) - */ - const clearAll = (): void => { - current.value = {} - opened.value = [] - keepAliveExclude.value = [] - } - - /** - * 获取状态快照(用于持久化存储) - */ - const getStateSnapshot = (): WorktabState => { - return { - current: { ...current.value }, - opened: [...opened.value], - keepAliveExclude: [...keepAliveExclude.value] - } - } - - /** - * 获取标签页标题 - */ - const getTabTitle = (path: string): WorkTab | undefined => { - const tab = getTab(path) - return tab - } - - /** - * 更新标签页标题 - */ - const updateTabTitle = (path: string, title: string): void => { - const tab = getTab(path) - if (tab) { - tab.customTitle = title - } - } - - /** - * 重置标签页标题 - */ - const resetTabTitle = (path: string): void => { - const tab = getTab(path) - if (tab) { - tab.customTitle = '' - } - } - - return { - // 状态 - current, - opened, - keepAliveExclude, - - // 计算属性 - hasOpenedTabs, - hasMultipleTabs, - currentTabIndex, - - // 方法 - openTab, - removeTab, - removeLeft, - removeRight, - removeOthers, - removeAll, - toggleFixedTab, - validateWorktabs, - clearAll, - getStateSnapshot, - - // 工具方法 - findTabIndex, - getTab, - isTabClosable, - addKeepAliveExclude, - removeKeepAliveExclude, - markTabsToRemove, - getTabTitle, - updateTabTitle, - resetTabTitle - } - }, - { - persist: { - key: 'worktab', - storage: localStorage - } - } + 'worktabStore', + () => { + // 状态定义 + const current = ref>({}) + const opened = ref([]) + const keepAliveExclude = ref([]) + + // 计算属性 + const hasOpenedTabs = computed(() => opened.value.length > 0) + const hasMultipleTabs = computed(() => opened.value.length > 1) + const currentTabIndex = computed(() => + current.value.path + ? opened.value.findIndex((tab) => tab.path === current.value.path) + : -1 + ) + + /** + * 查找标签页索引 + */ + const findTabIndex = (path: string): number => { + return opened.value.findIndex((tab) => tab.path === path) + } + + /** + * 获取标签页 + */ + const getTab = (path: string): WorkTab | undefined => { + return opened.value.find((tab) => tab.path === path) + } + + /** + * 检查标签页是否可关闭 + */ + const isTabClosable = (tab: WorkTab): boolean => { + return !tab.fixedTab + } + + /** + * 安全的路由跳转 + */ + const safeRouterPush = (tab: Partial): void => { + if (!tab.path) { + console.warn('尝试跳转到无效路径的标签页') + return + } + + try { + router.push({ + path: tab.path, + query: tab.query as LocationQueryRaw + }) + } catch (error) { + console.error('路由跳转失败:', error) + } + } + + /** + * 打开或激活一个选项卡 + */ + const openTab = (tab: WorkTab): void => { + if (!tab.path) { + console.warn('尝试打开无效的标签页') + return + } + + // 从 keepAlive 排除列表中移除 + if (tab.name) { + removeKeepAliveExclude(tab.name) + } + + // 先根据路由名称查找(应对动态路由参数导致的多开问题),找不到再根据路径查找 + let existingIndex = -1 + if (tab.name) { + existingIndex = opened.value.findIndex((t) => t.name === tab.name) + } + if (existingIndex === -1) { + existingIndex = findTabIndex(tab.path) + } + + if (existingIndex === -1) { + // 新增标签页 + const insertIndex = tab.fixedTab ? findFixedTabInsertIndex() : opened.value.length + const newTab = { ...tab } + + if (tab.fixedTab) { + opened.value.splice(insertIndex, 0, newTab) + } else { + opened.value.push(newTab) + } + + current.value = newTab + } else { + // 更新现有标签页(当动态路由参数或查询变更时,复用同一标签) + const existingTab = opened.value[existingIndex] + + opened.value[existingIndex] = { + ...existingTab, + path: tab.path, + params: tab.params, + query: tab.query, + title: tab.title || existingTab.title, + fixedTab: tab.fixedTab ?? existingTab.fixedTab, + keepAlive: tab.keepAlive ?? existingTab.keepAlive, + name: tab.name || existingTab.name, + icon: tab.icon || existingTab.icon + } + + current.value = opened.value[existingIndex] + } + } + + /** + * 查找固定标签页的插入位置 + */ + const findFixedTabInsertIndex = (): number => { + let insertIndex = 0 + for (let i = 0; i < opened.value.length; i++) { + if (opened.value[i].fixedTab) { + insertIndex = i + 1 + } else { + break + } + } + return insertIndex + } + + /** + * 关闭指定的选项卡 + */ + const removeTab = (path: string): void => { + const targetTab = getTab(path) + const targetIndex = findTabIndex(path) + + if (targetIndex === -1) { + console.warn(`尝试关闭不存在的标签页: ${path}`) + return + } + + if (targetTab && !isTabClosable(targetTab)) { + console.warn(`尝试关闭固定标签页: ${path}`) + return + } + + // 从标签页列表中移除 + opened.value.splice(targetIndex, 1) + + // 处理缓存排除 + if (targetTab?.name) { + addKeepAliveExclude(targetTab) + } + + const { homePath } = useCommon() + + // 如果关闭后无标签页,跳转首页 + if (!hasOpenedTabs.value) { + if (path !== homePath.value) { + current.value = {} + safeRouterPush({ path: homePath.value }) + } + return + } + + // 如果关闭的是当前激活标签,需要激活其他标签 + if (current.value.path === path) { + const newIndex = + targetIndex >= opened.value.length ? opened.value.length - 1 : targetIndex + current.value = opened.value[newIndex] + safeRouterPush(current.value) + } + } + + /** + * 关闭左侧选项卡 + */ + const removeLeft = (path: string): void => { + const targetIndex = findTabIndex(path) + + if (targetIndex === -1) { + console.warn(`尝试关闭左侧标签页,但目标标签页不存在: ${path}`) + return + } + + // 获取左侧可关闭的标签页 + const leftTabs = opened.value.slice(0, targetIndex) + const closableLeftTabs = leftTabs.filter(isTabClosable) + + if (closableLeftTabs.length === 0) { + console.warn('左侧没有可关闭的标签页') + return + } + + // 标记为缓存排除 + markTabsToRemove(closableLeftTabs) + + // 移除左侧可关闭的标签页 + opened.value = opened.value.filter( + (tab, index) => index >= targetIndex || !isTabClosable(tab) + ) + + // 确保当前标签是激活状态 + const targetTab = getTab(path) + if (targetTab) { + current.value = targetTab + } + } + + /** + * 关闭右侧选项卡 + */ + const removeRight = (path: string): void => { + const targetIndex = findTabIndex(path) + + if (targetIndex === -1) { + console.warn(`尝试关闭右侧标签页,但目标标签页不存在: ${path}`) + return + } + + // 获取右侧可关闭的标签页 + const rightTabs = opened.value.slice(targetIndex + 1) + const closableRightTabs = rightTabs.filter(isTabClosable) + + if (closableRightTabs.length === 0) { + console.warn('右侧没有可关闭的标签页') + return + } + + // 标记为缓存排除 + markTabsToRemove(closableRightTabs) + + // 移除右侧可关闭的标签页 + opened.value = opened.value.filter( + (tab, index) => index <= targetIndex || !isTabClosable(tab) + ) + + // 确保当前标签是激活状态 + const targetTab = getTab(path) + if (targetTab) { + current.value = targetTab + } + } + + /** + * 关闭其他选项卡 + */ + const removeOthers = (path: string): void => { + const targetTab = getTab(path) + + if (!targetTab) { + console.warn(`尝试关闭其他标签页,但目标标签页不存在: ${path}`) + return + } + + // 获取其他可关闭的标签页 + const otherTabs = opened.value.filter((tab) => tab.path !== path) + const closableTabs = otherTabs.filter(isTabClosable) + + if (closableTabs.length === 0) { + console.warn('没有其他可关闭的标签页') + return + } + + // 标记为缓存排除 + markTabsToRemove(closableTabs) + + // 只保留当前标签和固定标签 + opened.value = opened.value.filter((tab) => tab.path === path || !isTabClosable(tab)) + + // 确保当前标签是激活状态 + current.value = targetTab + } + + /** + * 关闭所有可关闭的标签页 + */ + const removeAll = (): void => { + const { homePath } = useCommon() + const hasFixedTabs = opened.value.some((tab) => tab.fixedTab) + + // 获取可关闭的标签页 + const closableTabs = opened.value.filter((tab) => { + if (!isTabClosable(tab)) return false + // 如果有固定标签,则所有可关闭的都可以关闭;否则保留首页 + return hasFixedTabs || tab.path !== homePath.value + }) + + if (closableTabs.length === 0) { + console.warn('没有可关闭的标签页') + return + } + + // 标记为缓存排除 + markTabsToRemove(closableTabs) + + // 保留不可关闭的标签页和首页(当没有固定标签时) + opened.value = opened.value.filter((tab) => { + return !isTabClosable(tab) || (!hasFixedTabs && tab.path === homePath.value) + }) + + // 处理激活状态 + if (!hasOpenedTabs.value) { + current.value = {} + safeRouterPush({ path: homePath.value }) + return + } + + // 选择激活的标签页:优先首页,其次第一个可用标签 + const homeTab = opened.value.find((tab) => tab.path === homePath.value) + const targetTab = homeTab || opened.value[0] + + current.value = targetTab + safeRouterPush(targetTab) + } + + /** + * 将指定选项卡添加到 keepAlive 排除列表中 + */ + const addKeepAliveExclude = (tab: WorkTab): void => { + if (!tab.keepAlive || !tab.name) return + + if (!keepAliveExclude.value.includes(tab.name)) { + keepAliveExclude.value.push(tab.name) + } + } + + /** + * 从 keepAlive 排除列表中移除指定组件名称 + */ + const removeKeepAliveExclude = (name: string): void => { + if (!name) return + + keepAliveExclude.value = keepAliveExclude.value.filter((item) => item !== name) + } + + /** + * 将传入的一组选项卡的组件名称标记为排除缓存 + */ + const markTabsToRemove = (tabs: WorkTab[]): void => { + tabs.forEach((tab) => { + if (tab.name) { + addKeepAliveExclude(tab) + } + }) + } + + /** + * 切换指定标签页的固定状态 + */ + const toggleFixedTab = (path: string): void => { + const targetIndex = findTabIndex(path) + + if (targetIndex === -1) { + console.warn(`尝试切换不存在标签页的固定状态: ${path}`) + return + } + + const tab = { ...opened.value[targetIndex] } + tab.fixedTab = !tab.fixedTab + + // 移除原位置 + opened.value.splice(targetIndex, 1) + + if (tab.fixedTab) { + // 固定标签插入到所有固定标签的末尾 + const firstNonFixedIndex = opened.value.findIndex((t) => !t.fixedTab) + const insertIndex = + firstNonFixedIndex === -1 ? opened.value.length : firstNonFixedIndex + opened.value.splice(insertIndex, 0, tab) + } else { + // 非固定标签插入到所有固定标签后 + const fixedCount = opened.value.filter((t) => t.fixedTab).length + opened.value.splice(fixedCount, 0, tab) + } + + // 更新当前标签引用 + if (current.value.path === path) { + current.value = tab + } + } + + /** + * 验证工作台标签页的路由有效性 + */ + const validateWorktabs = (routerInstance: Router): void => { + try { + // 动态路由校验:优先使用路由 name 判断有效性;否则用 resolve 匹配参数化路径 + const isTabRouteValid = (tab: Partial): boolean => { + try { + if (tab.name) { + const routes = routerInstance.getRoutes() + if (routes.some((r) => r.name === tab.name)) return true + } + if (tab.path) { + const resolved = routerInstance.resolve({ + path: tab.path, + query: (tab.query as LocationQueryRaw) || undefined + }) + return resolved.matched.length > 0 + } + return false + } catch { + return false + } + } + + // 过滤出有效的标签页 + const validTabs = opened.value.filter((tab) => isTabRouteValid(tab)) + + if (validTabs.length !== opened.value.length) { + console.warn('发现无效的标签页路由,已自动清理') + opened.value = validTabs + } + + // 验证当前激活标签的有效性 + const isCurrentValid = current.value && isTabRouteValid(current.value) + + if (!isCurrentValid && validTabs.length > 0) { + console.warn('当前激活标签无效,已自动切换') + current.value = validTabs[0] + } else if (!isCurrentValid) { + current.value = {} + } + } catch (error) { + console.error('验证工作台标签页失败:', error) + } + } + + /** + * 清空所有状态(用于登出等场景) + */ + const clearAll = (): void => { + current.value = {} + opened.value = [] + keepAliveExclude.value = [] + } + + /** + * 获取状态快照(用于持久化存储) + */ + const getStateSnapshot = (): WorktabState => { + return { + current: { ...current.value }, + opened: [...opened.value], + keepAliveExclude: [...keepAliveExclude.value] + } + } + + /** + * 获取标签页标题 + */ + const getTabTitle = (path: string): WorkTab | undefined => { + const tab = getTab(path) + return tab + } + + /** + * 更新标签页标题 + */ + const updateTabTitle = (path: string, title: string): void => { + const tab = getTab(path) + if (tab) { + tab.customTitle = title + } + } + + /** + * 重置标签页标题 + */ + const resetTabTitle = (path: string): void => { + const tab = getTab(path) + if (tab) { + tab.customTitle = '' + } + } + + return { + // 状态 + current, + opened, + keepAliveExclude, + + // 计算属性 + hasOpenedTabs, + hasMultipleTabs, + currentTabIndex, + + // 方法 + openTab, + removeTab, + removeLeft, + removeRight, + removeOthers, + removeAll, + toggleFixedTab, + validateWorktabs, + clearAll, + getStateSnapshot, + + // 工具方法 + findTabIndex, + getTab, + isTabClosable, + addKeepAliveExclude, + removeKeepAliveExclude, + markTabsToRemove, + getTabTitle, + updateTabTitle, + resetTabTitle + } + }, + { + persist: { + key: 'worktab', + storage: localStorage + } + } ) diff --git a/src/types/api/api.d.ts b/src/types/api/api.d.ts index fd3abbb..8e46be5 100644 --- a/src/types/api/api.d.ts +++ b/src/types/api/api.d.ts @@ -33,103 +33,106 @@ */ declare namespace Api { - /** 通用类型 */ - namespace Common { - /** 分页参数 */ - interface PaginationParams { - /** 当前页码 */ - current: number - /** 每页条数 */ - size: number - /** 总条数 */ - total: number - } + /** 通用类型 */ + namespace Common { + /** 分页参数 */ + interface PaginationParams { + /** 当前页码 */ + current: number + /** 每页条数 */ + size: number + /** 总条数 */ + total: number + } - /** 通用搜索参数 */ - type CommonSearchParams = Pick + /** 通用搜索参数 */ + type CommonSearchParams = Pick - /** 分页响应基础结构 */ - interface PaginatedResponse { - records: T[] - current: number - size: number - total: number - } + /** 分页响应基础结构 */ + interface PaginatedResponse { + records: T[] + current: number + size: number + total: number + } - /** 启用状态 */ - type EnableStatus = '1' | '2' - } + /** 启用状态 */ + type EnableStatus = '1' | '2' + } - /** 认证类型 */ - namespace Auth { - /** 登录参数 */ - interface LoginParams { - userName: string - password: string - } + /** 认证类型 */ + namespace Auth { + /** 登录参数 */ + interface LoginParams { + userName: string + password: string + } - /** 登录响应 */ - interface LoginResponse { - token: string - refreshToken: string - } + /** 登录响应 */ + interface LoginResponse { + token: string + refreshToken: string + } - /** 用户信息 */ - interface UserInfo { - buttons: string[] - roles: string[] - userId: number - userName: string - email: string - avatar?: string - } - } + /** 用户信息 */ + interface UserInfo { + buttons: string[] + roles: string[] + userId: number + userName: string + email: string + avatar?: string + } + } - /** 系统管理类型 */ - namespace SystemManage { - /** 用户列表 */ - type UserList = Api.Common.PaginatedResponse + /** 系统管理类型 */ + namespace SystemManage { + /** 用户列表 */ + type UserList = Api.Common.PaginatedResponse - /** 用户列表项 */ - interface UserListItem { - id: number - avatar: string - status: string - userName: string - userGender: string - nickName: string - userPhone: string - userEmail: string - userRoles: string[] - createBy: string - createTime: string - updateBy: string - updateTime: string - } + /** 用户列表项 */ + interface UserListItem { + id: number + avatar: string + status: string + userName: string + userGender: string + nickName: string + userPhone: string + userEmail: string + userRoles: string[] + createBy: string + createTime: string + updateBy: string + updateTime: string + } - /** 用户搜索参数 */ - type UserSearchParams = Partial< - Pick & - Api.Common.CommonSearchParams - > + /** 用户搜索参数 */ + type UserSearchParams = Partial< + Pick< + UserListItem, + 'id' | 'userName' | 'userGender' | 'userPhone' | 'userEmail' | 'status' + > & + Api.Common.CommonSearchParams + > - /** 角色列表 */ - type RoleList = Api.Common.PaginatedResponse + /** 角色列表 */ + type RoleList = Api.Common.PaginatedResponse - /** 角色列表项 */ - interface RoleListItem { - roleId: number - roleName: string - roleCode: string - description: string - enabled: boolean - createTime: string - } + /** 角色列表项 */ + interface RoleListItem { + roleId: number + roleName: string + roleCode: string + description: string + enabled: boolean + createTime: string + } - /** 角色搜索参数 */ - type RoleSearchParams = Partial< - Pick & - Api.Common.CommonSearchParams - > - } + /** 角色搜索参数 */ + type RoleSearchParams = Partial< + Pick & + Api.Common.CommonSearchParams + > + } } diff --git a/src/types/common/index.ts b/src/types/common/index.ts index 7e751d1..c3d3cc7 100644 --- a/src/types/common/index.ts +++ b/src/types/common/index.ts @@ -47,36 +47,36 @@ export type Recordable = Record // 键值对类型 export type KeyValue = { - key: string - value: T - label?: string + key: string + value: T + label?: string } // 时间范围类型 export interface TimeRange { - startTime: string - endTime: string + startTime: string + endTime: string } // 文件类型 export interface FileInfo { - name: string - url: string - size: number - type: string - lastModified?: number + name: string + url: string + size: number + type: string + lastModified?: number } // 坐标类型 export interface Position { - x: number - y: number + x: number + y: number } // 尺寸类型 export interface Size { - width: number - height: number + width: number + height: number } // 响应式断点类型 diff --git a/src/types/common/response.ts b/src/types/common/response.ts index 4a5fdab..2440b06 100644 --- a/src/types/common/response.ts +++ b/src/types/common/response.ts @@ -21,10 +21,10 @@ /** 基础 API 响应结构 */ export interface BaseResponse { - /** 状态码 */ - code: number - /** 消息 */ - msg: string - /** 数据 */ - data: T + /** 状态码 */ + code: number + /** 消息 */ + msg: string + /** 数据 */ + data: T } diff --git a/src/types/component/chart.ts b/src/types/component/chart.ts index c3225c9..2394464 100644 --- a/src/types/component/chart.ts +++ b/src/types/component/chart.ts @@ -33,278 +33,276 @@ import type { EChartsOption } from '@/plugins/echarts' export type LegendPosition = 'bottom' | 'top' | 'left' | 'right' export type SymbolType = - | 'circle' - | 'rect' - | 'roundRect' - | 'triangle' - | 'diamond' - | 'pin' - | 'arrow' - | 'none' + | 'circle' + | 'rect' + | 'roundRect' + | 'triangle' + | 'diamond' + | 'pin' + | 'arrow' + | 'none' // 图表主题配置 export interface ChartThemeConfig { - /** 图表高度 */ - chartHeight: string - /** 字体大小 */ - fontSize: number - /** 字体颜色 */ - fontColor: string - /** 主题颜色 */ - themeColor: string - /** 颜色组 */ - colors: string[] + /** 图表高度 */ + chartHeight: string + /** 字体大小 */ + fontSize: number + /** 字体颜色 */ + fontColor: string + /** 主题颜色 */ + themeColor: string + /** 颜色组 */ + colors: string[] } // 图表初始化选项 export interface UseChartOptions { - /** 初始化选项 */ - initOptions?: EChartsOption - /** 延迟初始化时间(ms) */ - initDelay?: number - /** IntersectionObserver阈值 */ - threshold?: number - /** 是否自动响应主题变化 */ - autoTheme?: boolean + /** 初始化选项 */ + initOptions?: EChartsOption + /** 延迟初始化时间(ms) */ + initDelay?: number + /** IntersectionObserver阈值 */ + threshold?: number + /** 是否自动响应主题变化 */ + autoTheme?: boolean } // 基础图表 Props 接口 - 统一所有图表的基础属性 export interface BaseChartProps { - /** 图表高度 */ - height?: string - /** 是否加载中 */ - loading?: boolean - isEmpty?: boolean - /** 颜色配置 */ - colors?: string[] + /** 图表高度 */ + height?: string + /** 是否加载中 */ + loading?: boolean + isEmpty?: boolean + /** 颜色配置 */ + colors?: string[] } // 轴线显示控制接口 - 统一轴线相关配置 export interface AxisDisplayProps { - /** 是否显示坐标轴标签 */ - showAxisLabel?: boolean - /** 是否显示坐标轴线 */ - showAxisLine?: boolean - /** 是否显示分割线 */ - showSplitLine?: boolean + /** 是否显示坐标轴标签 */ + showAxisLabel?: boolean + /** 是否显示坐标轴线 */ + showAxisLine?: boolean + /** 是否显示分割线 */ + showSplitLine?: boolean } // 交互显示控制接口 - 统一交互相关配置 export interface InteractionProps { - /** 是否显示提示框 */ - showTooltip?: boolean - /** 是否显示图例 */ - showLegend?: boolean - /** 图例位置 */ - legendPosition?: LegendPosition + /** 是否显示提示框 */ + showTooltip?: boolean + /** 是否显示图例 */ + showLegend?: boolean + /** 图例位置 */ + legendPosition?: LegendPosition } // 柱状图数据项接口 export interface BarDataItem { - /** 系列名称 */ - name: string - /** 数据值 */ - data: number[] - /** 柱状图宽度 */ - barWidth?: string | number - /** 堆叠分组名称 */ - stack?: string + /** 系列名称 */ + name: string + /** 数据值 */ + data: number[] + /** 柱状图宽度 */ + barWidth?: string | number + /** 堆叠分组名称 */ + stack?: string } // 柱状图 Props 接口 - 统一柱状图配置 export interface BarChartProps extends BaseChartProps, AxisDisplayProps, InteractionProps { - /** 图表数据 - 支持单组数据或多组数据 */ - data: number[] | BarDataItem[] - /** X轴标签数据 */ - xAxisData?: string[] - /** 柱状图宽度 */ - barWidth?: string | number - /** 是否堆叠显示 */ - stack?: boolean - /** 圆角 */ - borderRadius?: number | number[] + /** 图表数据 - 支持单组数据或多组数据 */ + data: number[] | BarDataItem[] + /** X轴标签数据 */ + xAxisData?: string[] + /** 柱状图宽度 */ + barWidth?: string | number + /** 是否堆叠显示 */ + stack?: boolean + /** 圆角 */ + borderRadius?: number | number[] } // 折线图数据项接口 export interface LineDataItem { - /** 系列名称 */ - name: string - /** 数据值 */ - data: number[] - /** 线条宽度 */ - lineWidth?: number - /** 是否显示区域填充 */ - showAreaColor?: boolean - /** 区域样式配置 */ - areaStyle?: { - /** 渐变开始透明度 */ - startOpacity?: number - /** 渐变结束透明度 */ - endOpacity?: number - /** 自定义 ECharts areaStyle 配置 */ - custom?: any - } - /** 是否平滑曲线 */ - smooth?: boolean - /** 数据点符号 */ - symbol?: SymbolType - /** 数据点大小 */ - symbolSize?: number + /** 系列名称 */ + name: string + /** 数据值 */ + data: number[] + /** 线条宽度 */ + lineWidth?: number + /** 是否显示区域填充 */ + showAreaColor?: boolean + /** 区域样式配置 */ + areaStyle?: { + /** 渐变开始透明度 */ + startOpacity?: number + /** 渐变结束透明度 */ + endOpacity?: number + /** 自定义 ECharts areaStyle 配置 */ + custom?: any + } + /** 是否平滑曲线 */ + smooth?: boolean + /** 数据点符号 */ + symbol?: SymbolType + /** 数据点大小 */ + symbolSize?: number } // 折线图 Props 接口 - 统一折线图配置 export interface LineChartProps extends BaseChartProps, AxisDisplayProps, InteractionProps { - /** 图表数据 - 支持单组数据或多组数据 */ - data: number[] | LineDataItem[] - /** X轴标签数据 */ - xAxisData?: string[] - /** 线条宽度 */ - lineWidth?: number - /** 是否显示区域填充 */ - showAreaColor?: boolean - /** 是否平滑曲线 */ - smooth?: boolean - /** 数据点符号 */ - symbol?: SymbolType - /** 数据点大小 */ - symbolSize?: number - /** 多数据动画延迟间隔(毫秒) */ - animationDelay?: number + /** 图表数据 - 支持单组数据或多组数据 */ + data: number[] | LineDataItem[] + /** X轴标签数据 */ + xAxisData?: string[] + /** 线条宽度 */ + lineWidth?: number + /** 是否显示区域填充 */ + showAreaColor?: boolean + /** 是否平滑曲线 */ + smooth?: boolean + /** 数据点符号 */ + symbol?: SymbolType + /** 数据点大小 */ + symbolSize?: number + /** 多数据动画延迟间隔(毫秒) */ + animationDelay?: number } // 雷达图数据项接口 export interface RadarDataItem { - /** 系列名称 */ - name: string - /** 数据值 */ - value: number[] + /** 系列名称 */ + name: string + /** 数据值 */ + value: number[] } // 雷达图 Props 接口 - 统一雷达图配置 export interface RadarChartProps extends BaseChartProps, InteractionProps { - /** 雷达图指标配置 */ - indicator?: Array<{ name: string; max: number }> - /** 图表数据 */ - data?: RadarDataItem[] + /** 雷达图指标配置 */ + indicator?: Array<{ name: string; max: number }> + /** 图表数据 */ + data?: RadarDataItem[] } // 饼图/环形图数据项接口 export interface PieDataItem { - /** 数据值 */ - value: number - /** 数据名称 */ - name: string + /** 数据值 */ + value: number + /** 数据名称 */ + name: string } // 环形图 Props 接口 - 统一环形图配置 export interface RingChartProps extends BaseChartProps, InteractionProps { - /** 图表数据 */ - data: PieDataItem[] - /** 内外半径 */ - radius?: string[] - /** 边框圆角 */ - borderRadius?: number - /** 中心文本 */ - centerText?: string - /** 是否显示标签 */ - showLabel?: boolean + /** 图表数据 */ + data: PieDataItem[] + /** 内外半径 */ + radius?: string[] + /** 边框圆角 */ + borderRadius?: number + /** 中心文本 */ + centerText?: string + /** 是否显示标签 */ + showLabel?: boolean } // K线图数据项接口 export interface KLineDataItem { - /** 时间标签 */ - time: string - /** 开盘价 */ - open: number - /** 收盘价 */ - close: number - /** 最高价 */ - high: number - /** 最低价 */ - low: number + /** 时间标签 */ + time: string + /** 开盘价 */ + open: number + /** 收盘价 */ + close: number + /** 最高价 */ + high: number + /** 最低价 */ + low: number } // K线图 Props 接口 - 统一K线图配置 export interface KLineChartProps extends BaseChartProps { - /** 图表数据 */ - data?: KLineDataItem[] - /** 是否显示数据缩放控件 */ - showDataZoom?: boolean - /** 数据缩放初始开始位置 */ - dataZoomStart?: number - /** 数据缩放初始结束位置 */ - dataZoomEnd?: number + /** 图表数据 */ + data?: KLineDataItem[] + /** 是否显示数据缩放控件 */ + showDataZoom?: boolean + /** 数据缩放初始开始位置 */ + dataZoomStart?: number + /** 数据缩放初始结束位置 */ + dataZoomEnd?: number } // 散点图数据项接口 export interface ScatterDataItem { - /** 坐标值 [x, y] */ - value: number[] + /** 坐标值 [x, y] */ + value: number[] } // 散点图 Props 接口 - 统一散点图配置 export interface ScatterChartProps extends BaseChartProps, AxisDisplayProps, InteractionProps { - /** 图表数据 */ - data?: ScatterDataItem[] - /** 散点大小 */ - symbolSize?: number + /** 图表数据 */ + data?: ScatterDataItem[] + /** 散点大小 */ + symbolSize?: number } // 双柱对比图 Props 接口 - 统一双柱对比图配置 export interface DualBarCompareChartProps extends BaseChartProps { - /** 上方数据 */ - topData: number[] - /** 下方数据 */ - bottomData: number[] - /** X轴标签数据 */ - xAxisData: string[] - /** 上方柱子颜色 */ - topColor?: string - /** 下方柱子颜色 */ - bottomColor?: string - /** 柱状图宽度 */ - barWidth?: number + /** 上方数据 */ + topData: number[] + /** 下方数据 */ + bottomData: number[] + /** X轴标签数据 */ + xAxisData: string[] + /** 上方柱子颜色 */ + topColor?: string + /** 下方柱子颜色 */ + bottomColor?: string + /** 柱状图宽度 */ + barWidth?: number } // 地图图表 Props 接口 - 统一地图图表配置 export interface MapChartProps extends BaseChartProps { - /** 地图数据 */ - mapData?: any[] - /** 选中区域 */ - selectedRegion?: string - /** 是否显示标签 */ - showLabels?: boolean - /** 是否显示散点 */ - showScatter?: boolean + /** 地图数据 */ + mapData?: any[] + /** 选中区域 */ + selectedRegion?: string + /** 是否显示标签 */ + showLabels?: boolean + /** 是否显示散点 */ + showScatter?: boolean } // 双向堆叠柱状图 Props 接口(人口金字塔样式) export interface BidirectionalBarChartProps - extends BaseChartProps, - AxisDisplayProps, - InteractionProps { - /** 正向数据(向上显示) */ - positiveData: number[] - /** 负向数据(向下显示) */ - negativeData: number[] - /** X轴标签数据 */ - xAxisData?: string[] - /** 正向数据名称 */ - positiveName?: string - /** 负向数据名称 */ - negativeName?: string - /** 柱状图宽度 */ - barWidth?: string | number - /** Y轴最小值 */ - yAxisMin?: number - /** Y轴最大值 */ - yAxisMax?: number - /** 是否显示数据标签 */ - showDataLabel?: boolean - /** 正向数据圆角配置 */ - positiveBorderRadius?: number | number[] - /** 负向数据圆角配置 */ - negativeBorderRadius?: number | number[] + extends BaseChartProps, AxisDisplayProps, InteractionProps { + /** 正向数据(向上显示) */ + positiveData: number[] + /** 负向数据(向下显示) */ + negativeData: number[] + /** X轴标签数据 */ + xAxisData?: string[] + /** 正向数据名称 */ + positiveName?: string + /** 负向数据名称 */ + negativeName?: string + /** 柱状图宽度 */ + barWidth?: string | number + /** Y轴最小值 */ + yAxisMin?: number + /** Y轴最大值 */ + yAxisMax?: number + /** 是否显示数据标签 */ + showDataLabel?: boolean + /** 正向数据圆角配置 */ + positiveBorderRadius?: number | number[] + /** 负向数据圆角配置 */ + negativeBorderRadius?: number | number[] } // 图表配置生成器函数类型 @@ -315,10 +313,10 @@ export type ChartEventCallback = (params: any) => void // 图表错误信息接口 export interface ChartError { - /** 错误码 */ - code: string - /** 错误信息 */ - message: string - /** 错误详情 */ - details?: any + /** 错误码 */ + code: string + /** 错误信息 */ + message: string + /** 错误详情 */ + details?: any } diff --git a/src/types/component/index.ts b/src/types/component/index.ts index cd89bce..fbbc896 100644 --- a/src/types/component/index.ts +++ b/src/types/component/index.ts @@ -23,123 +23,123 @@ // 搜索组件类型 export type SearchComponentType = - | 'input' - | 'select' - | 'radio' - | 'checkbox' - | 'date' - | 'datetime' - | 'daterange' - | 'datetimerange' - | 'month' - | 'monthrange' - | 'year' - | 'yearrange' - | 'week' - | 'time' - | 'timerange' + | 'input' + | 'select' + | 'radio' + | 'checkbox' + | 'date' + | 'datetime' + | 'daterange' + | 'datetimerange' + | 'month' + | 'monthrange' + | 'year' + | 'yearrange' + | 'week' + | 'time' + | 'timerange' // 搜索框值变化参数 export interface SearchChangeParams { - prop: string - val: unknown + prop: string + val: unknown } // 表格列配置接口 export interface ColumnOption { - // 列类型 - type?: 'selection' | 'expand' | 'index' | 'globalIndex' - // 列属性名 - prop?: string - // 列标题 - label?: string - // 列宽度 - width?: string | number - // 最小列宽度 - minWidth?: string | number - // 固定列 - fixed?: boolean | 'left' | 'right' - // 是否可排序 - sortable?: boolean - // 过滤器选项 - filters?: any[] - // 过滤方法 - filterMethod?: (value: any, row: any) => boolean - // 过滤器位置 - filterPlacement?: string - // 是否禁用 - disabled?: boolean - // 是否显示列 - visible?: boolean - // 是否选中显示 - checked?: boolean - // 自定义渲染函数 - formatter?: (row: T) => any - // 插槽相关配置 - // 是否使用插槽渲染内容 - useSlot?: boolean - // 插槽名称(默认为 prop 值) - slotName?: string - // 是否使用表头插槽 - useHeaderSlot?: boolean - // 表头插槽名称(默认为 `${prop}-header`) - headerSlotName?: string - // 其他属性 - [key: string]: any + // 列类型 + type?: 'selection' | 'expand' | 'index' | 'globalIndex' + // 列属性名 + prop?: string + // 列标题 + label?: string + // 列宽度 + width?: string | number + // 最小列宽度 + minWidth?: string | number + // 固定列 + fixed?: boolean | 'left' | 'right' + // 是否可排序 + sortable?: boolean + // 过滤器选项 + filters?: any[] + // 过滤方法 + filterMethod?: (value: any, row: any) => boolean + // 过滤器位置 + filterPlacement?: string + // 是否禁用 + disabled?: boolean + // 是否显示列 + visible?: boolean + // 是否选中显示 + checked?: boolean + // 自定义渲染函数 + formatter?: (row: T) => any + // 插槽相关配置 + // 是否使用插槽渲染内容 + useSlot?: boolean + // 插槽名称(默认为 prop 值) + slotName?: string + // 是否使用表头插槽 + useHeaderSlot?: boolean + // 表头插槽名称(默认为 `${prop}-header`) + headerSlotName?: string + // 其他属性 + [key: string]: any } // 分页配置 export interface PaginationConfig { - // 当前页 - currentPage: number - // 每页条数 - pageSize: number - // 总条数 - total: number - // 每页显示个数选择器的选项 - pageSizes?: number[] - // 组件布局 - layout?: string - // 是否为小型分页 - small?: boolean + // 当前页 + currentPage: number + // 每页条数 + pageSize: number + // 总条数 + total: number + // 每页显示个数选择器的选项 + pageSizes?: number[] + // 组件布局 + layout?: string + // 是否为小型分页 + small?: boolean } // 表单规则 export interface FormRule { - // 是否必填 - required?: boolean - // 错误提示信息 - message?: string - // 触发方式 - trigger?: string | string[] - // 最小长度 - min?: number - // 最大长度 - max?: number - // 正则表达式 - pattern?: RegExp - // 自定义验证函数 - validator?: (rule: any, value: any, callback: any) => void + // 是否必填 + required?: boolean + // 错误提示信息 + message?: string + // 触发方式 + trigger?: string | string[] + // 最小长度 + min?: number + // 最大长度 + max?: number + // 正则表达式 + pattern?: RegExp + // 自定义验证函数 + validator?: (rule: any, value: any, callback: any) => void } // 对话框配置 export interface DialogConfig { - // 标题 - title: string - // 是否显示 - visible: boolean - // 宽度 - width?: string | number - // 是否可以通过点击 modal 关闭 - closeOnClickModal?: boolean - // 是否可以通过按下 ESC 关闭 - closeOnPressEscape?: boolean - // 是否显示关闭按钮 - showClose?: boolean - // 是否在 Dialog 出现时将 body 滚动锁定 - lockScroll?: boolean - // 是否显示遮罩层 - modal?: boolean - // 自定义类名 - customClass?: string + // 标题 + title: string + // 是否显示 + visible: boolean + // 宽度 + width?: string | number + // 是否可以通过点击 modal 关闭 + closeOnClickModal?: boolean + // 是否可以通过按下 ESC 关闭 + closeOnPressEscape?: boolean + // 是否显示关闭按钮 + showClose?: boolean + // 是否在 Dialog 出现时将 body 滚动锁定 + lockScroll?: boolean + // 是否显示遮罩层 + modal?: boolean + // 自定义类名 + customClass?: string } diff --git a/src/types/config/index.ts b/src/types/config/index.ts index dd144de..333a519 100644 --- a/src/types/config/index.ts +++ b/src/types/config/index.ts @@ -29,86 +29,86 @@ import { MenuThemeType, SystemThemeTypes } from '@/types/store' // 主题设置 export interface ThemeSetting { - /** 主题名称 */ - name: string - /** 系统主题类型 */ - theme: SystemThemeEnum - /** 主题颜色数组 */ - color: string[] - /** 左侧线条颜色 */ - leftLineColor: string - /** 右侧线条颜色 */ - rightLineColor: string - /** 主题图片 */ - img: string + /** 主题名称 */ + name: string + /** 系统主题类型 */ + theme: SystemThemeEnum + /** 主题颜色数组 */ + color: string[] + /** 左侧线条颜色 */ + leftLineColor: string + /** 右侧线条颜色 */ + rightLineColor: string + /** 主题图片 */ + img: string } // 菜单布局 export interface MenuLayout { - /** 布局名称 */ - name: string - /** 菜单类型值 */ - value: MenuTypeEnum - /** 布局预览图 */ - img: string - /** 布局描述 */ - description?: string + /** 布局名称 */ + name: string + /** 菜单类型值 */ + value: MenuTypeEnum + /** 布局预览图 */ + img: string + /** 布局描述 */ + description?: string } // 节日配置 export interface FestivalConfig { - /** 节日日期(单日)或开始日期(日期范围) */ - date: string - /** 节日结束日期(可选,用于跨日期节日) */ - endDate?: string - /** 节日名称 */ - name: string - /** 烟花图片 */ - image: string - /** 滚动文本 */ - scrollText: string - /** 是否激活 */ - isActive?: boolean - /** 烟花播放次数(可选,默认为 3 次) */ - count?: number + /** 节日日期(单日)或开始日期(日期范围) */ + date: string + /** 节日结束日期(可选,用于跨日期节日) */ + endDate?: string + /** 节日名称 */ + name: string + /** 烟花图片 */ + image: string + /** 滚动文本 */ + scrollText: string + /** 是否激活 */ + isActive?: boolean + /** 烟花播放次数(可选,默认为 3 次) */ + count?: number } // 系统基础配置 export interface SystemBasicConfig { - // 系统名称 - name: string - // 系统描述 - description?: string - // 系统logo - logo?: string - // 系统favicon - favicon?: string - // 版权信息 - copyright?: string + // 系统名称 + name: string + // 系统描述 + description?: string + // 系统logo + logo?: string + // 系统favicon + favicon?: string + // 版权信息 + copyright?: string } // 快速入口基础项 export interface FastEnterBaseItem { - /** 名称 */ - name: string - /** 是否启用 */ - enabled?: boolean - /** 排序权重 */ - order?: number - /** 路由名称 */ - routeName?: string - /** 外部链接 */ - link?: string + /** 名称 */ + name: string + /** 是否启用 */ + enabled?: boolean + /** 排序权重 */ + order?: number + /** 路由名称 */ + routeName?: string + /** 外部链接 */ + link?: string } // 快速入口应用项 export interface FastEnterApplication extends FastEnterBaseItem { - /** 应用描述 */ - description: string - /** 图标代码 */ - icon: string - /** 图标颜色 */ - iconColor: string + /** 应用描述 */ + description: string + /** 图标代码 */ + icon: string + /** 图标颜色 */ + iconColor: string } // 快速链接项 @@ -116,96 +116,96 @@ export type FastEnterQuickLink = FastEnterBaseItem // 快速入口配置 export interface FastEnterConfig { - /** 应用列表 */ - applications: FastEnterApplication[] - /** 快速链接 */ - quickLinks: FastEnterQuickLink[] - /** 显示条件(屏幕宽度) */ - minWidth?: number + /** 应用列表 */ + applications: FastEnterApplication[] + /** 快速链接 */ + quickLinks: FastEnterQuickLink[] + /** 显示条件(屏幕宽度) */ + minWidth?: number } // 系统配置 export interface SystemConfig { - // 系统基础信息 - systemInfo: SystemBasicConfig - // 系统主题样式 - systemThemeStyles: SystemThemeTypes - // 设置主题列表 - settingThemeList: ThemeSetting[] - // 菜单布局列表 - menuLayoutList: MenuLayout[] - // 主题列表 - themeList: MenuThemeType[] - // 暗色菜单样式 - darkMenuStyles: MenuThemeType[] - // 系统主色调 - systemMainColor: readonly string[] - // 快速入口配置 - fastEnter?: FastEnterConfig - // 顶部栏功能配置 - headerBar?: HeaderBarFeatureConfig + // 系统基础信息 + systemInfo: SystemBasicConfig + // 系统主题样式 + systemThemeStyles: SystemThemeTypes + // 设置主题列表 + settingThemeList: ThemeSetting[] + // 菜单布局列表 + menuLayoutList: MenuLayout[] + // 主题列表 + themeList: MenuThemeType[] + // 暗色菜单样式 + darkMenuStyles: MenuThemeType[] + // 系统主色调 + systemMainColor: readonly string[] + // 快速入口配置 + fastEnter?: FastEnterConfig + // 顶部栏功能配置 + headerBar?: HeaderBarFeatureConfig } // 环境配置 export interface EnvConfig { - // 环境名称 - NODE_ENV: string - // 应用版本 - VITE_VERSION: string - // 应用端口 - VITE_PORT: string - // 应用基础路径 - VITE_BASE_URL: string - // API 地址 - VITE_API_URL: string - // 是否开启 Mock - VITE_USE_MOCK?: string - // 是否开启压缩 - VITE_USE_GZIP?: string - // 是否开启 CDN - VITE_USE_CDN?: string + // 环境名称 + NODE_ENV: string + // 应用版本 + VITE_VERSION: string + // 应用端口 + VITE_PORT: string + // 应用基础路径 + VITE_BASE_URL: string + // API 地址 + VITE_API_URL: string + // 是否开启 Mock + VITE_USE_MOCK?: string + // 是否开启压缩 + VITE_USE_GZIP?: string + // 是否开启 CDN + VITE_USE_CDN?: string } // 应用配置 export interface AppConfig extends SystemConfig { - // 环境配置 - env: EnvConfig - // 开发模式 - isDev: boolean - // 生产模式 - isProd: boolean - // 测试模式 - isTest: boolean + // 环境配置 + env: EnvConfig + // 开发模式 + isDev: boolean + // 生产模式 + isProd: boolean + // 测试模式 + isTest: boolean } // 功能配置项基础接口 export interface FeatureConfigItem { - enabled: boolean - description: string + enabled: boolean + description: string } // 顶部栏功能配置接口 export interface HeaderBarFeatureConfig { - /** 菜单按钮 */ - menuButton: FeatureConfigItem - /** 刷新按钮 */ - refreshButton: FeatureConfigItem - /** 快速入口 */ - fastEnter: FeatureConfigItem - /** 面包屑导航 */ - breadcrumb: FeatureConfigItem - /** 全局搜索 */ - globalSearch: FeatureConfigItem - /** 全屏功能 */ - fullscreen: FeatureConfigItem - /** 通知功能 */ - notification: FeatureConfigItem - /** 聊天功能 */ - chat: FeatureConfigItem - /** 多语言切换 */ - language: FeatureConfigItem - /** 设置面板 */ - settings: FeatureConfigItem - /** 主题切换 */ - themeToggle: FeatureConfigItem + /** 菜单按钮 */ + menuButton: FeatureConfigItem + /** 刷新按钮 */ + refreshButton: FeatureConfigItem + /** 快速入口 */ + fastEnter: FeatureConfigItem + /** 面包屑导航 */ + breadcrumb: FeatureConfigItem + /** 全局搜索 */ + globalSearch: FeatureConfigItem + /** 全屏功能 */ + fullscreen: FeatureConfigItem + /** 通知功能 */ + notification: FeatureConfigItem + /** 聊天功能 */ + chat: FeatureConfigItem + /** 多语言切换 */ + language: FeatureConfigItem + /** 设置面板 */ + settings: FeatureConfigItem + /** 主题切换 */ + themeToggle: FeatureConfigItem } diff --git a/src/types/router/index.ts b/src/types/router/index.ts index d9ef012..41098e8 100644 --- a/src/types/router/index.ts +++ b/src/types/router/index.ts @@ -27,45 +27,45 @@ import { RouteRecordRaw } from 'vue-router' * 定义路由的各种配置属性 */ export interface RouteMeta extends Record { - /** 路由标题 */ - title: string - /** 路由图标 */ - icon?: string - /** 是否显示徽章 */ - showBadge?: boolean - /** 文本徽章 */ - showTextBadge?: string - /** 是否在菜单中隐藏 */ - isHide?: boolean - /** 是否在标签页中隐藏 */ - isHideTab?: boolean - /** 外部链接 */ - link?: string - /** 是否为iframe */ - isIframe?: boolean - /** 是否缓存 */ - keepAlive?: boolean - /** 操作权限 */ - authList?: Array<{ - title: string - authMark: string - }> - /** 是否为一级菜单 */ - isFirstLevel?: boolean - /** 角色权限 */ - roles?: string[] - /** 是否固定标签页 */ - fixedTab?: boolean - /** 激活菜单路径 */ - activePath?: string - /** 是否为全屏页面 */ - isFullPage?: boolean - /** 是否为权限按钮行 */ - isAuthButton?: boolean - /** 权限标识 */ - authMark?: string - /** 父级路径 */ - parentPath?: string + /** 路由标题 */ + title: string + /** 路由图标 */ + icon?: string + /** 是否显示徽章 */ + showBadge?: boolean + /** 文本徽章 */ + showTextBadge?: string + /** 是否在菜单中隐藏 */ + isHide?: boolean + /** 是否在标签页中隐藏 */ + isHideTab?: boolean + /** 外部链接 */ + link?: string + /** 是否为iframe */ + isIframe?: boolean + /** 是否缓存 */ + keepAlive?: boolean + /** 操作权限 */ + authList?: Array<{ + title: string + authMark: string + }> + /** 是否为一级菜单 */ + isFirstLevel?: boolean + /** 角色权限 */ + roles?: string[] + /** 是否固定标签页 */ + fixedTab?: boolean + /** 激活菜单路径 */ + activePath?: string + /** 是否为全屏页面 */ + isFullPage?: boolean + /** 是否为权限按钮行 */ + isAuthButton?: boolean + /** 权限标识 */ + authMark?: string + /** 父级路径 */ + parentPath?: string } /** @@ -73,8 +73,8 @@ export interface RouteMeta extends Record { * 扩展 Vue Router 的路由记录类型 */ export interface AppRouteRecord extends Omit { - id?: number - meta: RouteMeta - children?: AppRouteRecord[] - component?: string | (() => Promise) + id?: number + meta: RouteMeta + children?: AppRouteRecord[] + component?: string | (() => Promise) } diff --git a/src/types/store/index.ts b/src/types/store/index.ts index 019801e..9b115a7 100644 --- a/src/types/store/index.ts +++ b/src/types/store/index.ts @@ -28,130 +28,130 @@ import { LocationQueryRaw } from 'vue-router' // 系统主题样式(light | dark) export interface SystemThemeType { - /** 主题类名 */ - className: string + /** 主题类名 */ + className: string } // 定义包含多个主题的类型 export type SystemThemeTypes = { - [key in Exclude]: SystemThemeType + [key in Exclude]: SystemThemeType } // 菜单主题样式 export interface MenuThemeType { - /** 主题类型 */ - theme: MenuThemeEnum - /** 背景颜色 */ - background: string - /** 系统名称颜色 */ - systemNameColor: string - /** 文本颜色 */ - textColor: string - /** 图标颜色 */ - iconColor: string - /** 背景图片 */ - img?: string + /** 主题类型 */ + theme: MenuThemeEnum + /** 背景颜色 */ + background: string + /** 系统名称颜色 */ + systemNameColor: string + /** 文本颜色 */ + textColor: string + /** 图标颜色 */ + iconColor: string + /** 背景图片 */ + img?: string } // 设置中心 export interface SettingState { - /** 主题 */ - theme: string - /** 是否只保持一个子菜单的展开 */ - uniqueOpened: boolean - /** 是否显示菜单按钮 */ - menuButton: boolean - /** 是否显示刷新按钮 */ - showRefreshButton: boolean - /** 是否显示面包屑 */ - showCrumbs: boolean - /** 是否自动关闭 */ - autoClose: boolean - /** 是否显示工作标签页 */ - showWorkTab: boolean - /** 是否显示语言切换 */ - showLanguage: boolean - /** 是否显示进度条 */ - showNprogress: boolean - /** 主题模式 */ - themeModel: string + /** 主题 */ + theme: string + /** 是否只保持一个子菜单的展开 */ + uniqueOpened: boolean + /** 是否显示菜单按钮 */ + menuButton: boolean + /** 是否显示刷新按钮 */ + showRefreshButton: boolean + /** 是否显示面包屑 */ + showCrumbs: boolean + /** 是否自动关闭 */ + autoClose: boolean + /** 是否显示工作标签页 */ + showWorkTab: boolean + /** 是否显示语言切换 */ + showLanguage: boolean + /** 是否显示进度条 */ + showNprogress: boolean + /** 主题模式 */ + themeModel: string } // 多标签 export interface WorkTab { - /** 标签标题 */ - title: string - /** 自定义标题 */ - customTitle?: string - /** 路由路径 */ - path: string - /** 路由名称 */ - name: string - /** 是否缓存 */ - keepAlive: boolean - /** 是否固定标签 */ - fixedTab?: boolean - /** 路由参数 */ - params?: object - /** 路由查询参数 */ - query?: LocationQueryRaw - /** 图标 */ - icon?: string - /** 是否激活 */ - isActive?: boolean + /** 标签标题 */ + title: string + /** 自定义标题 */ + customTitle?: string + /** 路由路径 */ + path: string + /** 路由名称 */ + name: string + /** 是否缓存 */ + keepAlive: boolean + /** 是否固定标签 */ + fixedTab?: boolean + /** 路由参数 */ + params?: object + /** 路由查询参数 */ + query?: LocationQueryRaw + /** 图标 */ + icon?: string + /** 是否激活 */ + isActive?: boolean } // 用户Store状态 export interface UserState { - /** 用户信息 */ - userInfo: Api.Auth.UserInfo | null - /** 认证令牌 */ - token: string | null - /** 用户角色列表 */ - roles: string[] - /** 用户权限列表 */ - permissions: string[] + /** 用户信息 */ + userInfo: Api.Auth.UserInfo | null + /** 认证令牌 */ + token: string | null + /** 用户角色列表 */ + roles: string[] + /** 用户权限列表 */ + permissions: string[] } // 设置Store状态 export interface SettingStoreState extends SettingState { - // 额外的设置状态 - /** 菜单是否折叠 */ - collapsed: boolean - /** 设备类型 */ - device: 'desktop' | 'mobile' - /** 当前语言 */ - language: string + // 额外的设置状态 + /** 菜单是否折叠 */ + collapsed: boolean + /** 设备类型 */ + device: 'desktop' | 'mobile' + /** 当前语言 */ + language: string } // 工作标签页Store状态 export interface WorkTabState { - /** 标签页列表 */ - tabs: WorkTab[] - /** 当前激活的标签页 */ - activeTab: string - /** 缓存的标签页列表 */ - cachedTabs: string[] + /** 标签页列表 */ + tabs: WorkTab[] + /** 当前激活的标签页 */ + activeTab: string + /** 缓存的标签页列表 */ + cachedTabs: string[] } // 菜单Store状态 export interface MenuState { - /** 菜单列表 */ - menuList: any[] - /** 菜单是否已加载 */ - isLoaded: boolean - /** 菜单是否折叠 */ - collapsed: boolean + /** 菜单列表 */ + menuList: any[] + /** 菜单是否已加载 */ + isLoaded: boolean + /** 菜单是否折叠 */ + collapsed: boolean } // 根Store状态类型 export interface RootState { - /** 用户状态 */ - user: UserState - /** 设置状态 */ - setting: SettingStoreState - /** 工作标签页状态 */ - workTab: WorkTabState - /** 菜单状态 */ - menu: MenuState + /** 用户状态 */ + user: UserState + /** 设置状态 */ + setting: SettingStoreState + /** 工作标签页状态 */ + workTab: WorkTabState + /** 菜单状态 */ + menu: MenuState } diff --git a/src/utils/constants/links.ts b/src/utils/constants/links.ts index 06d297e..4653ac8 100644 --- a/src/utils/constants/links.ts +++ b/src/utils/constants/links.ts @@ -6,30 +6,30 @@ * @author Art Design Pro Team */ export const WEB_LINKS = { - // Github 主页 - GITHUB_HOME: 'https://github.com/Daymychen/art-design-pro', + // Github 主页 + GITHUB_HOME: 'https://github.com/Daymychen/art-design-pro', - // 项目 Github 主页 - GITHUB: 'https://github.com/Daymychen/art-design-pro', + // 项目 Github 主页 + GITHUB: 'https://github.com/Daymychen/art-design-pro', - // 个人博客 - BLOG: 'https://www.artd.pro', + // 个人博客 + BLOG: 'https://www.artd.pro', - // 项目文档 - DOCS: 'https://www.artd.pro/docs/zh/', + // 项目文档 + DOCS: 'https://www.artd.pro/docs/zh/', - // 精简版本 - LiteVersion: 'https://www.artd.pro/docs/zh/guide/lite-version.html', + // 精简版本 + LiteVersion: 'https://www.artd.pro/docs/zh/guide/lite-version.html', - // v2.6.1版本 - OldVersion: 'https://www.artd.pro/v2/', + // v2.6.1版本 + OldVersion: 'https://www.artd.pro/v2/', - // 项目社区 - COMMUNITY: 'https://www.artd.pro/docs/zh/community/communicate.html', + // 项目社区 + COMMUNITY: 'https://www.artd.pro/docs/zh/community/communicate.html', - // 个人 Bilibili 主页 - BILIBILI: 'https://space.bilibili.com/425500936?spm_id_from=333.1007.0.0', + // 个人 Bilibili 主页 + BILIBILI: 'https://space.bilibili.com/425500936?spm_id_from=333.1007.0.0', - // 项目介绍 - INTRODUCE: 'https://www.artd.pro/docs/zh/guide/introduce.html' + // 项目介绍 + INTRODUCE: 'https://www.artd.pro/docs/zh/guide/introduce.html' } diff --git a/src/utils/form/responsive.ts b/src/utils/form/responsive.ts index c11df92..b7e0e41 100644 --- a/src/utils/form/responsive.ts +++ b/src/utils/form/responsive.ts @@ -43,21 +43,21 @@ export type ResponsiveBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' * 断点配置映射 */ interface BreakpointConfig { - /** 最小 span 阈值 */ - threshold: number - /** 降级后的 span 值 */ - fallback: number + /** 最小 span 阈值 */ + threshold: number + /** 降级后的 span 值 */ + fallback: number } /** * 响应式断点配置 */ const BREAKPOINT_CONFIG: Record = { - xs: { threshold: 12, fallback: 24 }, // 手机:小于 12 时使用满宽 - sm: { threshold: 12, fallback: 12 }, // 平板:小于 12 时使用半宽 - md: { threshold: 8, fallback: 8 }, // 中等屏幕:小于 8 时使用三分之一宽 - lg: null, // 大屏幕:直接使用设置的 span - xl: null // 超大屏幕:直接使用设置的 span + xs: { threshold: 12, fallback: 24 }, // 手机:小于 12 时使用满宽 + sm: { threshold: 12, fallback: 12 }, // 平板:小于 12 时使用半宽 + md: { threshold: 8, fallback: 8 }, // 中等屏幕:小于 8 时使用三分之一宽 + lg: null, // 大屏幕:直接使用设置的 span + xl: null // 超大屏幕:直接使用设置的 span } /** @@ -83,20 +83,20 @@ const BREAKPOINT_CONFIG: Record = * ``` */ export function calculateResponsiveSpan( - itemSpan: number | undefined, - defaultSpan: number, - breakpoint: ResponsiveBreakpoint + itemSpan: number | undefined, + defaultSpan: number, + breakpoint: ResponsiveBreakpoint ): number { - const finalSpan = itemSpan ?? defaultSpan - const config = BREAKPOINT_CONFIG[breakpoint] + const finalSpan = itemSpan ?? defaultSpan + const config = BREAKPOINT_CONFIG[breakpoint] - // 如果没有配置(lg/xl),直接返回原始 span - if (!config) { - return finalSpan - } + // 如果没有配置(lg/xl),直接返回原始 span + if (!config) { + return finalSpan + } - // 如果 span 小于阈值,使用降级值 - return finalSpan >= config.threshold ? finalSpan : config.fallback + // 如果 span 小于阈值,使用降级值 + return finalSpan >= config.threshold ? finalSpan : config.fallback } /** @@ -116,7 +116,7 @@ export function calculateResponsiveSpan( * ``` */ export function createResponsiveSpanCalculator(defaultSpan: number) { - return (itemSpan: number | undefined, breakpoint: ResponsiveBreakpoint): number => { - return calculateResponsiveSpan(itemSpan, defaultSpan, breakpoint) - } + return (itemSpan: number | undefined, breakpoint: ResponsiveBreakpoint): number => { + return calculateResponsiveSpan(itemSpan, defaultSpan, breakpoint) + } } diff --git a/src/utils/form/validator.ts b/src/utils/form/validator.ts index 3670763..9232121 100644 --- a/src/utils/form/validator.ts +++ b/src/utils/form/validator.ts @@ -34,9 +34,9 @@ * 密码强度级别枚举 */ export enum PasswordStrength { - WEAK = '弱', - MEDIUM = '中', - STRONG = '强' + WEAK = '弱', + MEDIUM = '中', + STRONG = '强' } /** @@ -45,10 +45,10 @@ export enum PasswordStrength { * @returns 返回去除首尾空格后的字符串 */ export function trimSpaces(value: string): string { - if (typeof value !== 'string') { - return '' - } - return value.trim() + if (typeof value !== 'string') { + return '' + } + return value.trim() } /** @@ -57,13 +57,13 @@ export function trimSpaces(value: string): string { * @returns 返回验证结果,true表示格式正确 */ export function validatePhone(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - // 中国大陆手机号码:1开头,第二位为3-9,共11位数字 - const phoneRegex = /^1[3-9]\d{9}$/ - return phoneRegex.test(value.trim()) + // 中国大陆手机号码:1开头,第二位为3-9,共11位数字 + const phoneRegex = /^1[3-9]\d{9}$/ + return phoneRegex.test(value.trim()) } /** @@ -72,13 +72,13 @@ export function validatePhone(value: string): boolean { * @returns 返回验证结果,true表示格式正确 */ export function validateTelPhone(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - // 支持格式:区号-号码,如:010-12345678、0755-1234567 - const telRegex = /^0\d{2,3}-?\d{7,8}$/ - return telRegex.test(value.trim().replace(/\s+/g, '')) + // 支持格式:区号-号码,如:010-12345678、0755-1234567 + const telRegex = /^0\d{2,3}-?\d{7,8}$/ + return telRegex.test(value.trim().replace(/\s+/g, '')) } /** @@ -88,13 +88,13 @@ export function validateTelPhone(value: string): boolean { * @description 规则:字母开头,5-20位,支持字母、数字、下划线 */ export function validateAccount(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - // 字母开头,5-20位,支持字母、数字、下划线 - const accountRegex = /^[a-zA-Z][a-zA-Z0-9_]{4,19}$/ - return accountRegex.test(value.trim()) + // 字母开头,5-20位,支持字母、数字、下划线 + const accountRegex = /^[a-zA-Z][a-zA-Z0-9_]{4,19}$/ + return accountRegex.test(value.trim()) } /** @@ -104,22 +104,22 @@ export function validateAccount(value: string): boolean { * @description 规则:6-20位,必须包含字母和数字 */ export function validatePassword(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - const trimmedValue = value.trim() + const trimmedValue = value.trim() - // 长度检查 - if (trimmedValue.length < 6 || trimmedValue.length > 20) { - return false - } + // 长度检查 + if (trimmedValue.length < 6 || trimmedValue.length > 20) { + return false + } - // 必须包含字母和数字 - const hasLetter = /[a-zA-Z]/.test(trimmedValue) - const hasNumber = /\d/.test(trimmedValue) + // 必须包含字母和数字 + const hasLetter = /[a-zA-Z]/.test(trimmedValue) + const hasNumber = /\d/.test(trimmedValue) - return hasLetter && hasNumber + return hasLetter && hasNumber } /** @@ -129,24 +129,24 @@ export function validatePassword(value: string): boolean { * @description 规则:8-20位,必须包含大写字母、小写字母、数字和特殊字符 */ export function validateStrongPassword(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - const trimmedValue = value.trim() + const trimmedValue = value.trim() - // 长度检查 - if (trimmedValue.length < 8 || trimmedValue.length > 20) { - return false - } + // 长度检查 + if (trimmedValue.length < 8 || trimmedValue.length > 20) { + return false + } - // 必须包含:大写字母、小写字母、数字、特殊字符 - const hasUpperCase = /[A-Z]/.test(trimmedValue) - const hasLowerCase = /[a-z]/.test(trimmedValue) - const hasNumber = /\d/.test(trimmedValue) - const hasSpecialChar = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(trimmedValue) + // 必须包含:大写字母、小写字母、数字、特殊字符 + const hasUpperCase = /[A-Z]/.test(trimmedValue) + const hasLowerCase = /[a-z]/.test(trimmedValue) + const hasNumber = /\d/.test(trimmedValue) + const hasSpecialChar = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(trimmedValue) - return hasUpperCase && hasLowerCase && hasNumber && hasSpecialChar + return hasUpperCase && hasLowerCase && hasNumber && hasSpecialChar } /** @@ -156,30 +156,30 @@ export function validateStrongPassword(value: string): boolean { * @description 弱:纯数字/纯字母/纯特殊字符;中:两种组合;强:三种或以上组合 */ export function getPasswordStrength(value: string): PasswordStrength { - if (!value || typeof value !== 'string') { - return PasswordStrength.WEAK - } + if (!value || typeof value !== 'string') { + return PasswordStrength.WEAK + } - const trimmedValue = value.trim() + const trimmedValue = value.trim() - if (trimmedValue.length < 6) { - return PasswordStrength.WEAK - } + if (trimmedValue.length < 6) { + return PasswordStrength.WEAK + } - const hasUpperCase = /[A-Z]/.test(trimmedValue) - const hasLowerCase = /[a-z]/.test(trimmedValue) - const hasNumber = /\d/.test(trimmedValue) - const hasSpecialChar = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(trimmedValue) + const hasUpperCase = /[A-Z]/.test(trimmedValue) + const hasLowerCase = /[a-z]/.test(trimmedValue) + const hasNumber = /\d/.test(trimmedValue) + const hasSpecialChar = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(trimmedValue) - const typeCount = [hasUpperCase, hasLowerCase, hasNumber, hasSpecialChar].filter(Boolean).length + const typeCount = [hasUpperCase, hasLowerCase, hasNumber, hasSpecialChar].filter(Boolean).length - if (typeCount >= 3) { - return PasswordStrength.STRONG - } else if (typeCount >= 2) { - return PasswordStrength.MEDIUM - } else { - return PasswordStrength.WEAK - } + if (typeCount >= 3) { + return PasswordStrength.STRONG + } else if (typeCount >= 2) { + return PasswordStrength.MEDIUM + } else { + return PasswordStrength.WEAK + } } /** @@ -188,23 +188,23 @@ export function getPasswordStrength(value: string): PasswordStrength { * @returns 返回验证结果,true表示格式正确 */ export function validateIPv4Address(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - const trimmedValue = value.trim() - const ipRegex = /^((25[0-5]|2[0-4]\d|[01]?\d{1,2})\.){3}(25[0-5]|2[0-4]\d|[01]?\d{1,2})$/ + const trimmedValue = value.trim() + const ipRegex = /^((25[0-5]|2[0-4]\d|[01]?\d{1,2})\.){3}(25[0-5]|2[0-4]\d|[01]?\d{1,2})$/ - if (!ipRegex.test(trimmedValue)) { - return false - } + if (!ipRegex.test(trimmedValue)) { + return false + } - // 额外检查每个段是否在有效范围内 - const segments = trimmedValue.split('.') - return segments.every((segment) => { - const num = parseInt(segment, 10) - return num >= 0 && num <= 255 - }) + // 额外检查每个段是否在有效范围内 + const segments = trimmedValue.split('.') + return segments.every((segment) => { + const num = parseInt(segment, 10) + return num >= 0 && num <= 255 + }) } /** @@ -213,17 +213,17 @@ export function validateIPv4Address(value: string): boolean { * @returns 返回验证结果,true表示格式正确 */ export function validateEmail(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - const trimmedValue = value.trim() + const trimmedValue = value.trim() - // RFC 5322 标准的简化版邮箱正则 - const emailRegex = - /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ + // RFC 5322 标准的简化版邮箱正则 + const emailRegex = + /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ - return emailRegex.test(trimmedValue) && trimmedValue.length <= 254 + return emailRegex.test(trimmedValue) && trimmedValue.length <= 254 } /** @@ -232,16 +232,16 @@ export function validateEmail(value: string): boolean { * @returns 返回验证结果,true表示格式正确 */ export function validateURL(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - try { - new URL(value.trim()) - return true - } catch { - return false - } + try { + new URL(value.trim()) + return true + } catch { + return false + } } /** @@ -250,31 +250,31 @@ export function validateURL(value: string): boolean { * @returns 返回验证结果,true表示格式正确 */ export function validateChineseIDCard(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - const trimmedValue = value.trim() + const trimmedValue = value.trim() - // 18位身份证号码正则 - const idCardRegex = - /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/ + // 18位身份证号码正则 + const idCardRegex = + /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/ - if (!idCardRegex.test(trimmedValue)) { - return false - } + if (!idCardRegex.test(trimmedValue)) { + return false + } - // 验证校验码 - const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] - const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'] + // 验证校验码 + const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'] - let sum = 0 - for (let i = 0; i < 17; i++) { - sum += parseInt(trimmedValue[i]) * weights[i] - } + let sum = 0 + for (let i = 0; i < 17; i++) { + sum += parseInt(trimmedValue[i]) * weights[i] + } - const checkCode = checkCodes[sum % 11] - return trimmedValue[17].toUpperCase() === checkCode + const checkCode = checkCodes[sum % 11] + return trimmedValue[17].toUpperCase() === checkCode } /** @@ -283,34 +283,34 @@ export function validateChineseIDCard(value: string): boolean { * @returns 返回验证结果,true表示格式正确 */ export function validateBankCard(value: string): boolean { - if (!value || typeof value !== 'string') { - return false - } + if (!value || typeof value !== 'string') { + return false + } - const trimmedValue = value.trim().replace(/\s+/g, '') + const trimmedValue = value.trim().replace(/\s+/g, '') - // 银行卡号通常为13-19位数字 - if (!/^\d{13,19}$/.test(trimmedValue)) { - return false - } + // 银行卡号通常为13-19位数字 + if (!/^\d{13,19}$/.test(trimmedValue)) { + return false + } - // Luhn算法验证 - let sum = 0 - let shouldDouble = false + // Luhn算法验证 + let sum = 0 + let shouldDouble = false - for (let i = trimmedValue.length - 1; i >= 0; i--) { - let digit = parseInt(trimmedValue[i]) + for (let i = trimmedValue.length - 1; i >= 0; i--) { + let digit = parseInt(trimmedValue[i]) - if (shouldDouble) { - digit *= 2 - if (digit > 9) { - digit = (digit % 10) + 1 - } - } + if (shouldDouble) { + digit *= 2 + if (digit > 9) { + digit = (digit % 10) + 1 + } + } - sum += digit - shouldDouble = !shouldDouble - } + sum += digit + shouldDouble = !shouldDouble + } - return sum % 10 === 0 + return sum % 10 === 0 } diff --git a/src/utils/http/error.ts b/src/utils/http/error.ts index 0f2c1ae..946a3a1 100644 --- a/src/utils/http/error.ts +++ b/src/utils/http/error.ts @@ -27,69 +27,69 @@ import { $t } from '@/locales' // 错误响应接口 export interface ErrorResponse { - /** 错误状态码 */ - code: number - /** 错误消息 */ - msg: string - /** 错误附加数据 */ - data?: unknown + /** 错误状态码 */ + code: number + /** 错误消息 */ + msg: string + /** 错误附加数据 */ + data?: unknown } // 错误日志数据接口 export interface ErrorLogData { - /** 错误状态码 */ - code: number - /** 错误消息 */ - message: string - /** 错误附加数据 */ - data?: unknown - /** 错误发生时间戳 */ - timestamp: string - /** 请求 URL */ - url?: string - /** 请求方法 */ - method?: string - /** 错误堆栈信息 */ - stack?: string + /** 错误状态码 */ + code: number + /** 错误消息 */ + message: string + /** 错误附加数据 */ + data?: unknown + /** 错误发生时间戳 */ + timestamp: string + /** 请求 URL */ + url?: string + /** 请求方法 */ + method?: string + /** 错误堆栈信息 */ + stack?: string } // 自定义 HttpError 类 export class HttpError extends Error { - public readonly code: number - public readonly data?: unknown - public readonly timestamp: string - public readonly url?: string - public readonly method?: string + public readonly code: number + public readonly data?: unknown + public readonly timestamp: string + public readonly url?: string + public readonly method?: string - constructor( - message: string, - code: number, - options?: { - data?: unknown - url?: string - method?: string - } - ) { - super(message) - this.name = 'HttpError' - this.code = code - this.data = options?.data - this.timestamp = new Date().toISOString() - this.url = options?.url - this.method = options?.method - } + constructor( + message: string, + code: number, + options?: { + data?: unknown + url?: string + method?: string + } + ) { + super(message) + this.name = 'HttpError' + this.code = code + this.data = options?.data + this.timestamp = new Date().toISOString() + this.url = options?.url + this.method = options?.method + } - public toLogData(): ErrorLogData { - return { - code: this.code, - message: this.message, - data: this.data, - timestamp: this.timestamp, - url: this.url, - method: this.method, - stack: this.stack - } - } + public toLogData(): ErrorLogData { + return { + code: this.code, + message: this.message, + data: this.data, + timestamp: this.timestamp, + url: this.url, + method: this.method, + stack: this.stack + } + } } /** @@ -98,19 +98,19 @@ export class HttpError extends Error { * @returns 错误消息 */ const getErrorMessage = (status: number): string => { - const errorMap: Record = { - [ApiStatus.unauthorized]: 'httpMsg.unauthorized', - [ApiStatus.forbidden]: 'httpMsg.forbidden', - [ApiStatus.notFound]: 'httpMsg.notFound', - [ApiStatus.methodNotAllowed]: 'httpMsg.methodNotAllowed', - [ApiStatus.requestTimeout]: 'httpMsg.requestTimeout', - [ApiStatus.internalServerError]: 'httpMsg.internalServerError', - [ApiStatus.badGateway]: 'httpMsg.badGateway', - [ApiStatus.serviceUnavailable]: 'httpMsg.serviceUnavailable', - [ApiStatus.gatewayTimeout]: 'httpMsg.gatewayTimeout' - } + const errorMap: Record = { + [ApiStatus.unauthorized]: 'httpMsg.unauthorized', + [ApiStatus.forbidden]: 'httpMsg.forbidden', + [ApiStatus.notFound]: 'httpMsg.notFound', + [ApiStatus.methodNotAllowed]: 'httpMsg.methodNotAllowed', + [ApiStatus.requestTimeout]: 'httpMsg.requestTimeout', + [ApiStatus.internalServerError]: 'httpMsg.internalServerError', + [ApiStatus.badGateway]: 'httpMsg.badGateway', + [ApiStatus.serviceUnavailable]: 'httpMsg.serviceUnavailable', + [ApiStatus.gatewayTimeout]: 'httpMsg.gatewayTimeout' + } - return $t(errorMap[status] || 'httpMsg.internalServerError') + return $t(errorMap[status] || 'httpMsg.internalServerError') } /** @@ -119,33 +119,33 @@ const getErrorMessage = (status: number): string => { * @returns 错误对象 */ export function handleError(error: AxiosError): never { - // 处理取消的请求 - if (error.code === 'ERR_CANCELED') { - console.warn('Request cancelled:', error.message) - throw new HttpError($t('httpMsg.requestCancelled'), ApiStatus.error) - } + // 处理取消的请求 + if (error.code === 'ERR_CANCELED') { + console.warn('Request cancelled:', error.message) + throw new HttpError($t('httpMsg.requestCancelled'), ApiStatus.error) + } - const statusCode = error.response?.status - const errorMessage = error.response?.data?.msg || error.message - const requestConfig = error.config + const statusCode = error.response?.status + const errorMessage = error.response?.data?.msg || error.message + const requestConfig = error.config - // 处理网络错误 - if (!error.response) { - throw new HttpError($t('httpMsg.networkError'), ApiStatus.error, { - url: requestConfig?.url, - method: requestConfig?.method?.toUpperCase() - }) - } + // 处理网络错误 + if (!error.response) { + throw new HttpError($t('httpMsg.networkError'), ApiStatus.error, { + url: requestConfig?.url, + method: requestConfig?.method?.toUpperCase() + }) + } - // 处理 HTTP 状态码错误 - const message = statusCode - ? getErrorMessage(statusCode) - : errorMessage || $t('httpMsg.requestFailed') - throw new HttpError(message, statusCode || ApiStatus.error, { - data: error.response.data, - url: requestConfig?.url, - method: requestConfig?.method?.toUpperCase() - }) + // 处理 HTTP 状态码错误 + const message = statusCode + ? getErrorMessage(statusCode) + : errorMessage || $t('httpMsg.requestFailed') + throw new HttpError(message, statusCode || ApiStatus.error, { + data: error.response.data, + url: requestConfig?.url, + method: requestConfig?.method?.toUpperCase() + }) } /** @@ -154,11 +154,11 @@ export function handleError(error: AxiosError): never { * @param showMessage 是否显示错误消息 */ export function showError(error: HttpError, showMessage: boolean = true): void { - if (showMessage) { - ElMessage.error(error.message) - } - // 记录错误日志 - console.error('[HTTP Error]', error.toLogData()) + if (showMessage) { + ElMessage.error(error.message) + } + // 记录错误日志 + console.error('[HTTP Error]', error.toLogData()) } /** @@ -167,9 +167,9 @@ export function showError(error: HttpError, showMessage: boolean = true): void { * @param showMessage 是否显示消息 */ export function showSuccess(message: string, showMessage: boolean = true): void { - if (showMessage) { - ElMessage.success(message) - } + if (showMessage) { + ElMessage.success(message) + } } /** @@ -178,5 +178,5 @@ export function showSuccess(message: string, showMessage: boolean = true): void * @returns 是否为 HttpError 类型 */ export const isHttpError = (error: unknown): error is HttpError => { - return error instanceof HttpError + return error instanceof HttpError } diff --git a/src/utils/http/index.ts b/src/utils/http/index.ts index 0e0002a..3e8a3b5 100644 --- a/src/utils/http/index.ts +++ b/src/utils/http/index.ts @@ -34,181 +34,185 @@ let unauthorizedTimer: NodeJS.Timeout | null = null /** 扩展 AxiosRequestConfig */ interface ExtendedAxiosRequestConfig extends AxiosRequestConfig { - showErrorMessage?: boolean - showSuccessMessage?: boolean + showErrorMessage?: boolean + showSuccessMessage?: boolean } const { VITE_API_URL, VITE_WITH_CREDENTIALS } = import.meta.env /** Axios实例 */ const axiosInstance = axios.create({ - timeout: REQUEST_TIMEOUT, - baseURL: VITE_API_URL, - withCredentials: VITE_WITH_CREDENTIALS === 'true', - validateStatus: (status) => status >= 200 && status < 300, - transformResponse: [ - (data, headers) => { - const contentType = headers['content-type'] - if (contentType?.includes('application/json')) { - try { - return JSON.parse(data) - } catch { - return data - } - } - return data - } - ] + timeout: REQUEST_TIMEOUT, + baseURL: VITE_API_URL, + withCredentials: VITE_WITH_CREDENTIALS === 'true', + validateStatus: (status) => status >= 200 && status < 300, + transformResponse: [ + (data, headers) => { + const contentType = headers['content-type'] + if (contentType?.includes('application/json')) { + try { + return JSON.parse(data) + } catch { + return data + } + } + return data + } + ] }) /** 请求拦截器 */ axiosInstance.interceptors.request.use( - (request: InternalAxiosRequestConfig) => { - const { accessToken } = useUserStore() - if (accessToken) request.headers.set('Authorization', accessToken) + (request: InternalAxiosRequestConfig) => { + const { accessToken } = useUserStore() + if (accessToken) request.headers.set('Authorization', accessToken) - if (request.data && !(request.data instanceof FormData) && !request.headers['Content-Type']) { - request.headers.set('Content-Type', 'application/json') - request.data = JSON.stringify(request.data) - } + if ( + request.data && + !(request.data instanceof FormData) && + !request.headers['Content-Type'] + ) { + request.headers.set('Content-Type', 'application/json') + request.data = JSON.stringify(request.data) + } - return request - }, - (error) => { - showError(createHttpError($t('httpMsg.requestConfigError'), ApiStatus.error)) - return Promise.reject(error) - } + return request + }, + (error) => { + showError(createHttpError($t('httpMsg.requestConfigError'), ApiStatus.error)) + return Promise.reject(error) + } ) /** 响应拦截器 */ axiosInstance.interceptors.response.use( - (response: AxiosResponse) => { - const { code, msg } = response.data - if (code === ApiStatus.success) return response - if (code === ApiStatus.unauthorized) handleUnauthorizedError(msg) - throw createHttpError(msg || $t('httpMsg.requestFailed'), code) - }, - (error) => { - if (error.response?.status === ApiStatus.unauthorized) handleUnauthorizedError() - return Promise.reject(handleError(error)) - } + (response: AxiosResponse) => { + const { code, msg } = response.data + if (code === ApiStatus.success) return response + if (code === ApiStatus.unauthorized) handleUnauthorizedError(msg) + throw createHttpError(msg || $t('httpMsg.requestFailed'), code) + }, + (error) => { + if (error.response?.status === ApiStatus.unauthorized) handleUnauthorizedError() + return Promise.reject(handleError(error)) + } ) /** 统一创建HttpError */ function createHttpError(message: string, code: number) { - return new HttpError(message, code) + return new HttpError(message, code) } /** 处理401错误(带防抖) */ function handleUnauthorizedError(message?: string): never { - const error = createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized) + const error = createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized) - if (!isUnauthorizedErrorShown) { - isUnauthorizedErrorShown = true - logOut() + if (!isUnauthorizedErrorShown) { + isUnauthorizedErrorShown = true + logOut() - unauthorizedTimer = setTimeout(resetUnauthorizedError, UNAUTHORIZED_DEBOUNCE_TIME) + unauthorizedTimer = setTimeout(resetUnauthorizedError, UNAUTHORIZED_DEBOUNCE_TIME) - showError(error, true) - throw error - } + showError(error, true) + throw error + } - throw error + throw error } /** 重置401防抖状态 */ function resetUnauthorizedError() { - isUnauthorizedErrorShown = false - if (unauthorizedTimer) clearTimeout(unauthorizedTimer) - unauthorizedTimer = null + isUnauthorizedErrorShown = false + if (unauthorizedTimer) clearTimeout(unauthorizedTimer) + unauthorizedTimer = null } /** 退出登录函数 */ function logOut() { - setTimeout(() => { - useUserStore().logOut() - }, LOGOUT_DELAY) + setTimeout(() => { + useUserStore().logOut() + }, LOGOUT_DELAY) } /** 是否需要重试 */ function shouldRetry(statusCode: number) { - return [ - ApiStatus.requestTimeout, - ApiStatus.internalServerError, - ApiStatus.badGateway, - ApiStatus.serviceUnavailable, - ApiStatus.gatewayTimeout - ].includes(statusCode) + return [ + ApiStatus.requestTimeout, + ApiStatus.internalServerError, + ApiStatus.badGateway, + ApiStatus.serviceUnavailable, + ApiStatus.gatewayTimeout + ].includes(statusCode) } /** 请求重试逻辑 */ async function retryRequest( - config: ExtendedAxiosRequestConfig, - retries: number = MAX_RETRIES + config: ExtendedAxiosRequestConfig, + retries: number = MAX_RETRIES ): Promise { - try { - return await request(config) - } catch (error) { - if (retries > 0 && error instanceof HttpError && shouldRetry(error.code)) { - await delay(RETRY_DELAY) - return retryRequest(config, retries - 1) - } - throw error - } + try { + return await request(config) + } catch (error) { + if (retries > 0 && error instanceof HttpError && shouldRetry(error.code)) { + await delay(RETRY_DELAY) + return retryRequest(config, retries - 1) + } + throw error + } } /** 延迟函数 */ function delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)) } /** 请求函数 */ async function request(config: ExtendedAxiosRequestConfig): Promise { - // POST | PUT 参数自动填充 - if ( - ['POST', 'PUT'].includes(config.method?.toUpperCase() || '') && - config.params && - !config.data - ) { - config.data = config.params - config.params = undefined - } + // POST | PUT 参数自动填充 + if ( + ['POST', 'PUT'].includes(config.method?.toUpperCase() || '') && + config.params && + !config.data + ) { + config.data = config.params + config.params = undefined + } - try { - const res = await axiosInstance.request>(config) + try { + const res = await axiosInstance.request>(config) - // 显示成功消息 - if (config.showSuccessMessage && res.data.msg) { - showSuccess(res.data.msg) - } + // 显示成功消息 + if (config.showSuccessMessage && res.data.msg) { + showSuccess(res.data.msg) + } - return res.data.data as T - } catch (error) { - if (error instanceof HttpError && error.code !== ApiStatus.unauthorized) { - const showMsg = config.showErrorMessage !== false - showError(error, showMsg) - } - return Promise.reject(error) - } + return res.data.data as T + } catch (error) { + if (error instanceof HttpError && error.code !== ApiStatus.unauthorized) { + const showMsg = config.showErrorMessage !== false + showError(error, showMsg) + } + return Promise.reject(error) + } } /** API方法集合 */ const api = { - get(config: ExtendedAxiosRequestConfig) { - return retryRequest({ ...config, method: 'GET' }) - }, - post(config: ExtendedAxiosRequestConfig) { - return retryRequest({ ...config, method: 'POST' }) - }, - put(config: ExtendedAxiosRequestConfig) { - return retryRequest({ ...config, method: 'PUT' }) - }, - del(config: ExtendedAxiosRequestConfig) { - return retryRequest({ ...config, method: 'DELETE' }) - }, - request(config: ExtendedAxiosRequestConfig) { - return retryRequest(config) - } + get(config: ExtendedAxiosRequestConfig) { + return retryRequest({ ...config, method: 'GET' }) + }, + post(config: ExtendedAxiosRequestConfig) { + return retryRequest({ ...config, method: 'POST' }) + }, + put(config: ExtendedAxiosRequestConfig) { + return retryRequest({ ...config, method: 'PUT' }) + }, + del(config: ExtendedAxiosRequestConfig) { + return retryRequest({ ...config, method: 'DELETE' }) + }, + request(config: ExtendedAxiosRequestConfig) { + return retryRequest(config) + } } export default api diff --git a/src/utils/http/status.ts b/src/utils/http/status.ts index 989bb37..9e343fd 100644 --- a/src/utils/http/status.ts +++ b/src/utils/http/status.ts @@ -2,17 +2,17 @@ * 接口状态码 */ export enum ApiStatus { - success = 200, // 成功 - error = 400, // 错误 - unauthorized = 401, // 未授权 - forbidden = 403, // 禁止访问 - notFound = 404, // 未找到 - methodNotAllowed = 405, // 方法不允许 - requestTimeout = 408, // 请求超时 - internalServerError = 500, // 服务器错误 - notImplemented = 501, // 未实现 - badGateway = 502, // 网关错误 - serviceUnavailable = 503, // 服务不可用 - gatewayTimeout = 504, // 网关超时 - httpVersionNotSupported = 505 // HTTP版本不支持 + success = 200, // 成功 + error = 400, // 错误 + unauthorized = 401, // 未授权 + forbidden = 403, // 禁止访问 + notFound = 404, // 未找到 + methodNotAllowed = 405, // 方法不允许 + requestTimeout = 408, // 请求超时 + internalServerError = 500, // 服务器错误 + notImplemented = 501, // 未实现 + badGateway = 502, // 网关错误 + serviceUnavailable = 503, // 服务不可用 + gatewayTimeout = 504, // 网关超时 + httpVersionNotSupported = 505 // HTTP版本不支持 } diff --git a/src/utils/navigation/jump.ts b/src/utils/navigation/jump.ts index 4fde1e8..0fdde2e 100644 --- a/src/utils/navigation/jump.ts +++ b/src/utils/navigation/jump.ts @@ -19,7 +19,7 @@ import { router } from '@/router' // 打开外部链接 export const openExternalLink = (link: string) => { - window.open(link, '_blank') + window.open(link, '_blank') } /** @@ -29,34 +29,34 @@ export const openExternalLink = (link: string) => { * @returns */ export const handleMenuJump = (item: AppRouteRecord, jumpToFirst: boolean = false) => { - // 处理外部链接 - const { link, isIframe } = item.meta - if (link && !isIframe) { - return openExternalLink(link) - } + // 处理外部链接 + const { link, isIframe } = item.meta + if (link && !isIframe) { + return openExternalLink(link) + } - // 如果不需要跳转到第一个子菜单,或者没有子菜单,直接跳转当前路径 - if (!jumpToFirst || !item.children?.length) { - return router.push(item.path) - } + // 如果不需要跳转到第一个子菜单,或者没有子菜单,直接跳转当前路径 + if (!jumpToFirst || !item.children?.length) { + return router.push(item.path) + } - // 递归查找第一个可见的叶子节点菜单 - const findFirstLeafMenu = (items: AppRouteRecord[]): AppRouteRecord => { - for (const child of items) { - if (!child.meta.isHide) { - return child.children?.length ? findFirstLeafMenu(child.children) : child - } - } - return items[0] - } + // 递归查找第一个可见的叶子节点菜单 + const findFirstLeafMenu = (items: AppRouteRecord[]): AppRouteRecord => { + for (const child of items) { + if (!child.meta.isHide) { + return child.children?.length ? findFirstLeafMenu(child.children) : child + } + } + return items[0] + } - const firstChild = findFirstLeafMenu(item.children) + const firstChild = findFirstLeafMenu(item.children) - // 如果第一个子菜单是外部链接则打开新窗口 - if (firstChild.meta?.link) { - return openExternalLink(firstChild.meta.link) - } + // 如果第一个子菜单是外部链接则打开新窗口 + if (firstChild.meta?.link) { + return openExternalLink(firstChild.meta.link) + } - // 跳转到子菜单路径 - router.push(firstChild.path) + // 跳转到子菜单路径 + router.push(firstChild.path) } diff --git a/src/utils/navigation/route.ts b/src/utils/navigation/route.ts index 9ca4f29..6877055 100644 --- a/src/utils/navigation/route.ts +++ b/src/utils/navigation/route.ts @@ -26,7 +26,7 @@ import { AppRouteRecord } from '@/types' // 检查是否为 iframe 路由 export function isIframe(url: string): boolean { - return url.startsWith('/outside/iframe/') + return url.startsWith('/outside/iframe/') } /** @@ -35,7 +35,7 @@ export function isIframe(url: string): boolean { * @returns 是否为有效菜单项 */ const isValidMenuItem = (menuItem: AppRouteRecord): boolean => { - return !!(menuItem.path && menuItem.path.trim() && !menuItem.meta?.isHide) + return !!(menuItem.path && menuItem.path.trim() && !menuItem.meta?.isHide) } /** @@ -44,7 +44,7 @@ const isValidMenuItem = (menuItem: AppRouteRecord): boolean => { * @returns 标准化后的路径 */ const normalizePath = (path: string): string => { - return path.startsWith('/') ? path : `/${path}` + return path.startsWith('/') ? path : `/${path}` } /** @@ -53,26 +53,26 @@ const normalizePath = (path: string): string => { * @returns 第一个有效路径,如果没有找到则返回空字符串 */ export const getFirstMenuPath = (menuList: AppRouteRecord[]): string => { - if (!Array.isArray(menuList) || menuList.length === 0) { - return '' - } + if (!Array.isArray(menuList) || menuList.length === 0) { + return '' + } - for (const menuItem of menuList) { - if (!isValidMenuItem(menuItem)) { - continue - } + for (const menuItem of menuList) { + if (!isValidMenuItem(menuItem)) { + continue + } - // 如果有子菜单,优先查找子菜单 - if (menuItem.children?.length) { - const childPath = getFirstMenuPath(menuItem.children) - if (childPath) { - return childPath - } - } + // 如果有子菜单,优先查找子菜单 + if (menuItem.children?.length) { + const childPath = getFirstMenuPath(menuItem.children) + if (childPath) { + return childPath + } + } - // 返回当前菜单项的标准化路径 - return normalizePath(menuItem.path!) - } + // 返回当前菜单项的标准化路径 + return normalizePath(menuItem.path!) + } - return '' + return '' } diff --git a/src/utils/navigation/worktab.ts b/src/utils/navigation/worktab.ts index 6db6a77..288f92f 100644 --- a/src/utils/navigation/worktab.ts +++ b/src/utils/navigation/worktab.ts @@ -33,35 +33,35 @@ import { useCommon } from '@/hooks/core/useCommon' * @param to 当前路由对象 */ export const setWorktab = (to: RouteLocationNormalized): void => { - const worktabStore = useWorktabStore() - const { meta, path, name, params, query } = to - if (!meta.isHideTab) { - // 如果是 iframe 页面,则特殊处理工作标签页 - if (isIframe(path)) { - const iframeRoute = IframeRouteManager.getInstance().findByPath(to.path) + const worktabStore = useWorktabStore() + const { meta, path, name, params, query } = to + if (!meta.isHideTab) { + // 如果是 iframe 页面,则特殊处理工作标签页 + if (isIframe(path)) { + const iframeRoute = IframeRouteManager.getInstance().findByPath(to.path) - if (iframeRoute?.meta) { - worktabStore.openTab({ - title: iframeRoute.meta.title, - icon: meta.icon as string, - path, - name: name as string, - keepAlive: meta.keepAlive as boolean, - params, - query - }) - } - } else if (useSettingStore().showWorkTab || path === useCommon().homePath.value) { - worktabStore.openTab({ - title: meta.title as string, - icon: meta.icon as string, - path, - name: name as string, - keepAlive: meta.keepAlive as boolean, - params, - query, - fixedTab: meta.fixedTab as boolean - }) - } - } + if (iframeRoute?.meta) { + worktabStore.openTab({ + title: iframeRoute.meta.title, + icon: meta.icon as string, + path, + name: name as string, + keepAlive: meta.keepAlive as boolean, + params, + query + }) + } + } else if (useSettingStore().showWorkTab || path === useCommon().homePath.value) { + worktabStore.openTab({ + title: meta.title as string, + icon: meta.icon as string, + path, + name: name as string, + keepAlive: meta.keepAlive as boolean, + params, + query, + fixedTab: meta.fixedTab as boolean + }) + } + } } diff --git a/src/utils/router.ts b/src/utils/router.ts index 8c838ff..47d6d35 100644 --- a/src/utils/router.ts +++ b/src/utils/router.ts @@ -13,17 +13,17 @@ import i18n, { $t } from '@/locales' /** 扩展的路由配置类型 */ export type AppRouteRecordRaw = RouteRecordRaw & { - hidden?: boolean + hidden?: boolean } /** 顶部进度条配置 */ export const configureNProgress = () => { - NProgress.configure({ - easing: 'ease', - speed: 600, - showSpinner: false, - parent: 'body' - }) + NProgress.configure({ + easing: 'ease', + speed: 600, + showSpinner: false, + parent: 'body' + }) } /** @@ -31,12 +31,12 @@ export const configureNProgress = () => { * @param to 当前路由对象 */ export const setPageTitle = (to: RouteLocationNormalized): void => { - const { title } = to.meta - if (title) { - setTimeout(() => { - document.title = `${formatMenuTitle(String(title))} - ${AppConfig.systemInfo.name}` - }, 150) - } + const { title } = to.meta + if (title) { + setTimeout(() => { + document.title = `${formatMenuTitle(String(title))} - ${AppConfig.systemInfo.name}` + }, 150) + } } /** @@ -45,17 +45,17 @@ export const setPageTitle = (to: RouteLocationNormalized): void => { * @returns 格式化后的菜单标题 */ export const formatMenuTitle = (title: string): string => { - if (title) { - if (title.startsWith('menus.')) { - // 使用 te() 方法检查翻译键值是否存在,避免控制台警告 - if (i18n.global.te(title)) { - return $t(title) - } else { - // 如果翻译不存在,返回键值的最后部分作为fallback - return title.split('.').pop() || title - } - } - return title - } - return '' + if (title) { + if (title.startsWith('menus.')) { + // 使用 te() 方法检查翻译键值是否存在,避免控制台警告 + if (i18n.global.te(title)) { + return $t(title) + } else { + // 如果翻译不存在,返回键值的最后部分作为fallback + return title.split('.').pop() || title + } + } + return title + } + return '' } diff --git a/src/utils/socket/index.ts b/src/utils/socket/index.ts index 77d46ad..5c753f4 100644 --- a/src/utils/socket/index.ts +++ b/src/utils/socket/index.ts @@ -1,388 +1,391 @@ interface WebSocketOptions { - url?: string - messageHandler: (event: MessageEvent) => void - reconnectInterval?: number // 重连间隔(ms) - heartbeatInterval?: number // 心跳检测间隔(ms) - pingInterval?: number // 发送ping间隔(ms) - reconnectTimeout?: number // 重连超时时间(ms) - maxReconnectAttempts?: number // 最大重连次数 - connectionTimeout?: number // 连接建立超时时间(ms) + url?: string + messageHandler: (event: MessageEvent) => void + reconnectInterval?: number // 重连间隔(ms) + heartbeatInterval?: number // 心跳检测间隔(ms) + pingInterval?: number // 发送ping间隔(ms) + reconnectTimeout?: number // 重连超时时间(ms) + maxReconnectAttempts?: number // 最大重连次数 + connectionTimeout?: number // 连接建立超时时间(ms) } export default class WebSocketClient { - private static instance: WebSocketClient | null = null - private ws: WebSocket | null = null - private url: string - private messageHandler: (event: MessageEvent) => void - private reconnectInterval: number - private heartbeatInterval: number - private pingInterval: number - private reconnectTimeout: number - private maxReconnectAttempts: number - private connectionTimeout: number - private reconnectAttempts: number = 0 // 当前重连次数 + private static instance: WebSocketClient | null = null + private ws: WebSocket | null = null + private url: string + private messageHandler: (event: MessageEvent) => void + private reconnectInterval: number + private heartbeatInterval: number + private pingInterval: number + private reconnectTimeout: number + private maxReconnectAttempts: number + private connectionTimeout: number + private reconnectAttempts: number = 0 // 当前重连次数 - // 消息队列 - 缓存连接建立前的消息 - private messageQueue: Array = [] + // 消息队列 - 缓存连接建立前的消息 + private messageQueue: Array = [] - // 定时器 - private detectionTimer: NodeJS.Timeout | null = null - private timeoutTimer: NodeJS.Timeout | null = null - private reconnectTimer: NodeJS.Timeout | null = null - private pingTimer: NodeJS.Timeout | null = null - private connectionTimer: NodeJS.Timeout | null = null // 连接超时定时器 + // 定时器 + private detectionTimer: NodeJS.Timeout | null = null + private timeoutTimer: NodeJS.Timeout | null = null + private reconnectTimer: NodeJS.Timeout | null = null + private pingTimer: NodeJS.Timeout | null = null + private connectionTimer: NodeJS.Timeout | null = null // 连接超时定时器 - // 状态标识 - private isConnected: boolean = false - private isConnecting: boolean = false // 是否正在连接中 - private stopReconnect: boolean = false + // 状态标识 + private isConnected: boolean = false + private isConnecting: boolean = false // 是否正在连接中 + private stopReconnect: boolean = false - private constructor(options: WebSocketOptions) { - this.url = options.url || (process.env.VUE_APP_LOGIN_WEBSOCKET as string) - this.messageHandler = options.messageHandler - this.reconnectInterval = options.reconnectInterval || 20 * 1000 // 默认20秒 - this.heartbeatInterval = options.heartbeatInterval || 5 * 1000 // 默认5秒 - this.pingInterval = options.pingInterval || 10 * 1000 // 默认10秒 - this.reconnectTimeout = options.reconnectTimeout || 30 * 1000 // 默认30秒 - this.maxReconnectAttempts = options.maxReconnectAttempts || 10 // 默认最多重连10次 - this.connectionTimeout = options.connectionTimeout || 10 * 1000 // 连接超时10秒 - } + private constructor(options: WebSocketOptions) { + this.url = options.url || (process.env.VUE_APP_LOGIN_WEBSOCKET as string) + this.messageHandler = options.messageHandler + this.reconnectInterval = options.reconnectInterval || 20 * 1000 // 默认20秒 + this.heartbeatInterval = options.heartbeatInterval || 5 * 1000 // 默认5秒 + this.pingInterval = options.pingInterval || 10 * 1000 // 默认10秒 + this.reconnectTimeout = options.reconnectTimeout || 30 * 1000 // 默认30秒 + this.maxReconnectAttempts = options.maxReconnectAttempts || 10 // 默认最多重连10次 + this.connectionTimeout = options.connectionTimeout || 10 * 1000 // 连接超时10秒 + } - // 单例模式获取实例 - static getInstance(options: WebSocketOptions): WebSocketClient { - if (!WebSocketClient.instance) { - WebSocketClient.instance = new WebSocketClient(options) - } else { - // 更新消息处理器 - WebSocketClient.instance.messageHandler = options.messageHandler - // 如果提供了新的URL,则更新并重新连接 - if (options.url && WebSocketClient.instance.url !== options.url) { - WebSocketClient.instance.url = options.url - WebSocketClient.instance.reconnectAttempts = 0 - WebSocketClient.instance.init() - } - } - return WebSocketClient.instance - } + // 单例模式获取实例 + static getInstance(options: WebSocketOptions): WebSocketClient { + if (!WebSocketClient.instance) { + WebSocketClient.instance = new WebSocketClient(options) + } else { + // 更新消息处理器 + WebSocketClient.instance.messageHandler = options.messageHandler + // 如果提供了新的URL,则更新并重新连接 + if (options.url && WebSocketClient.instance.url !== options.url) { + WebSocketClient.instance.url = options.url + WebSocketClient.instance.reconnectAttempts = 0 + WebSocketClient.instance.init() + } + } + return WebSocketClient.instance + } - // 初始化连接 - init(): void { - // 如果正在连接中,不重复连接 - if (this.isConnecting) { - console.log('正在建立WebSocket连接中...') - return - } + // 初始化连接 + init(): void { + // 如果正在连接中,不重复连接 + if (this.isConnecting) { + console.log('正在建立WebSocket连接中...') + return + } - // 如果已连接,不重复连接 - if (this.ws?.readyState === WebSocket.OPEN) { - console.warn('WebSocket连接已存在') - this.flushMessageQueue() // 确保队列中的消息被发送 - return - } + // 如果已连接,不重复连接 + if (this.ws?.readyState === WebSocket.OPEN) { + console.warn('WebSocket连接已存在') + this.flushMessageQueue() // 确保队列中的消息被发送 + return + } - try { - this.isConnecting = true - this.reconnectAttempts = 0 // 重置重连次数 - this.ws = new WebSocket(this.url) + try { + this.isConnecting = true + this.reconnectAttempts = 0 // 重置重连次数 + this.ws = new WebSocket(this.url) - // 设置连接超时检测 - this.clearTimer('connectionTimer') - this.connectionTimer = setTimeout(() => { - console.error(`WebSocket连接超时 (${this.connectionTimeout}ms):${this.url}`) - this.handleConnectionTimeout() - }, this.connectionTimeout) + // 设置连接超时检测 + this.clearTimer('connectionTimer') + this.connectionTimer = setTimeout(() => { + console.error(`WebSocket连接超时 (${this.connectionTimeout}ms):${this.url}`) + this.handleConnectionTimeout() + }, this.connectionTimeout) - this.ws.onopen = (event) => this.handleOpen(event) - this.ws.onmessage = (event) => this.handleMessage(event) - this.ws.onclose = (event) => this.handleClose(event) - this.ws.onerror = (event) => this.handleError(event) - } catch (error) { - console.error('WebSocket初始化失败:', error) - this.isConnecting = false - this.reconnect() - } - } + this.ws.onopen = (event) => this.handleOpen(event) + this.ws.onmessage = (event) => this.handleMessage(event) + this.ws.onclose = (event) => this.handleClose(event) + this.ws.onerror = (event) => this.handleError(event) + } catch (error) { + console.error('WebSocket初始化失败:', error) + this.isConnecting = false + this.reconnect() + } + } - // 处理连接超时 - private handleConnectionTimeout(): void { - if (this.ws?.readyState !== WebSocket.OPEN) { - console.error('WebSocket连接超时,强制关闭连接') - this.ws?.close(1000, 'Connection timeout') - this.isConnecting = false - this.reconnect() - } - } + // 处理连接超时 + private handleConnectionTimeout(): void { + if (this.ws?.readyState !== WebSocket.OPEN) { + console.error('WebSocket连接超时,强制关闭连接') + this.ws?.close(1000, 'Connection timeout') + this.isConnecting = false + this.reconnect() + } + } - // 关闭连接 - close(force?: boolean): void { - this.clearAllTimers() - this.stopReconnect = true - this.isConnecting = false + // 关闭连接 + close(force?: boolean): void { + this.clearAllTimers() + this.stopReconnect = true + this.isConnecting = false - if (this.ws) { - // 1000 表示正常关闭 - this.ws.close(force ? 1001 : 1000, force ? 'Force closed' : 'Normal close') - this.ws = null - } + if (this.ws) { + // 1000 表示正常关闭 + this.ws.close(force ? 1001 : 1000, force ? 'Force closed' : 'Normal close') + this.ws = null + } - this.isConnected = false - } + this.isConnected = false + } - // 发送消息 - 增加消息队列 - send(data: string | ArrayBufferLike | Blob | ArrayBufferView, immediate: boolean = false): void { - // 如果要求立即发送且未连接,则直接报错 - if (immediate && (!this.ws || this.ws.readyState !== WebSocket.OPEN)) { - console.error('WebSocket未连接,无法立即发送消息') - return - } + // 发送消息 - 增加消息队列 + send( + data: string | ArrayBufferLike | Blob | ArrayBufferView, + immediate: boolean = false + ): void { + // 如果要求立即发送且未连接,则直接报错 + if (immediate && (!this.ws || this.ws.readyState !== WebSocket.OPEN)) { + console.error('WebSocket未连接,无法立即发送消息') + return + } - // 如果未连接且不要求立即发送,则加入消息队列 - if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { - console.log('WebSocket未连接,消息已加入队列等待发送') - this.messageQueue.push(data) - // 如果未在重连中,则尝试重连 - if (!this.isConnecting && !this.stopReconnect) { - this.init() - } - return - } + // 如果未连接且不要求立即发送,则加入消息队列 + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { + console.log('WebSocket未连接,消息已加入队列等待发送') + this.messageQueue.push(data) + // 如果未在重连中,则尝试重连 + if (!this.isConnecting && !this.stopReconnect) { + this.init() + } + return + } - try { - this.ws.send(data) - } catch (error) { - console.error('WebSocket发送消息失败:', error) - // 发送失败时将消息加入队列,等待重连后重试 - this.messageQueue.push(data) - this.reconnect() - } - } + try { + this.ws.send(data) + } catch (error) { + console.error('WebSocket发送消息失败:', error) + // 发送失败时将消息加入队列,等待重连后重试 + this.messageQueue.push(data) + this.reconnect() + } + } - // 发送队列中的消息 - private flushMessageQueue(): void { - if (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) { - console.log(`发送队列中的${this.messageQueue.length}条消息`) - while (this.messageQueue.length > 0) { - const data = this.messageQueue.shift() - if (data) { - try { - this.ws?.send(data) - } catch (error) { - console.error('发送队列消息失败:', error) - // 如果发送失败,将消息放回队列头部 - if (data) this.messageQueue.unshift(data) - break - } - } - } - } - } + // 发送队列中的消息 + private flushMessageQueue(): void { + if (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) { + console.log(`发送队列中的${this.messageQueue.length}条消息`) + while (this.messageQueue.length > 0) { + const data = this.messageQueue.shift() + if (data) { + try { + this.ws?.send(data) + } catch (error) { + console.error('发送队列消息失败:', error) + // 如果发送失败,将消息放回队列头部 + if (data) this.messageQueue.unshift(data) + break + } + } + } + } + } - // 处理连接打开 - private handleOpen(event: Event): void { - console.log('WebSocket连接成功', event) - this.clearTimer('connectionTimer') // 清除连接超时定时器 - this.isConnected = true - this.isConnecting = false - this.stopReconnect = false - this.reconnectAttempts = 0 // 重置重连次数 - this.startHeartbeat() - this.startPing() - this.flushMessageQueue() // 发送队列中的消息 - } + // 处理连接打开 + private handleOpen(event: Event): void { + console.log('WebSocket连接成功', event) + this.clearTimer('connectionTimer') // 清除连接超时定时器 + this.isConnected = true + this.isConnecting = false + this.stopReconnect = false + this.reconnectAttempts = 0 // 重置重连次数 + this.startHeartbeat() + this.startPing() + this.flushMessageQueue() // 发送队列中的消息 + } - // 处理收到的消息 - private handleMessage(event: MessageEvent): void { - console.log('收到WebSocket消息:', event) - this.resetHeartbeat() - this.messageHandler(event) - } + // 处理收到的消息 + private handleMessage(event: MessageEvent): void { + console.log('收到WebSocket消息:', event) + this.resetHeartbeat() + this.messageHandler(event) + } - // 处理连接关闭 - private handleClose(event: CloseEvent): void { - console.log( - `WebSocket断开: 代码=${event.code}, 原因=${event.reason}, 干净关闭=${event.wasClean}` - ) + // 处理连接关闭 + private handleClose(event: CloseEvent): void { + console.log( + `WebSocket断开: 代码=${event.code}, 原因=${event.reason}, 干净关闭=${event.wasClean}` + ) - // 1000 是正常关闭代码 - const isNormalClose = event.code === 1000 + // 1000 是正常关闭代码 + const isNormalClose = event.code === 1000 - this.isConnected = false - this.isConnecting = false - this.clearAllTimers() + this.isConnected = false + this.isConnecting = false + this.clearAllTimers() - if (!this.stopReconnect && !isNormalClose) { - this.reconnect() - } - } + if (!this.stopReconnect && !isNormalClose) { + this.reconnect() + } + } - // 处理错误 - 增加详细错误信息 - private handleError(event: Event): void { - console.error('WebSocket连接错误:') - console.error('错误事件:', event) - console.error( - '当前连接状态:', - this.ws?.readyState ? this.getReadyStateText(this.ws.readyState) : '未初始化' - ) + // 处理错误 - 增加详细错误信息 + private handleError(event: Event): void { + console.error('WebSocket连接错误:') + console.error('错误事件:', event) + console.error( + '当前连接状态:', + this.ws?.readyState ? this.getReadyStateText(this.ws.readyState) : '未初始化' + ) - this.isConnected = false - this.isConnecting = false + this.isConnected = false + this.isConnecting = false - // 只有在未停止重连的情况下才尝试重连 - if (!this.stopReconnect) { - this.reconnect() - } - } + // 只有在未停止重连的情况下才尝试重连 + if (!this.stopReconnect) { + this.reconnect() + } + } - // 转换连接状态为文本描述 - private getReadyStateText(state: number): string { - switch (state) { - case WebSocket.CONNECTING: - return 'CONNECTING (0) - 正在连接' - case WebSocket.OPEN: - return 'OPEN (1) - 已连接' - case WebSocket.CLOSING: - return 'CLOSING (2) - 正在关闭' - case WebSocket.CLOSED: - return 'CLOSED (3) - 已关闭' - default: - return `未知状态 (${state})` - } - } + // 转换连接状态为文本描述 + private getReadyStateText(state: number): string { + switch (state) { + case WebSocket.CONNECTING: + return 'CONNECTING (0) - 正在连接' + case WebSocket.OPEN: + return 'OPEN (1) - 已连接' + case WebSocket.CLOSING: + return 'CLOSING (2) - 正在关闭' + case WebSocket.CLOSED: + return 'CLOSED (3) - 已关闭' + default: + return `未知状态 (${state})` + } + } - // 开始心跳检测 - private startHeartbeat(): void { - this.clearTimer('detectionTimer') - this.clearTimer('timeoutTimer') + // 开始心跳检测 + private startHeartbeat(): void { + this.clearTimer('detectionTimer') + this.clearTimer('timeoutTimer') - this.detectionTimer = setTimeout(() => { - this.isConnected = this.ws?.readyState === WebSocket.OPEN + this.detectionTimer = setTimeout(() => { + this.isConnected = this.ws?.readyState === WebSocket.OPEN - if (!this.isConnected) { - console.warn('WebSocket心跳检测失败,尝试重连') - this.reconnect() + if (!this.isConnected) { + console.warn('WebSocket心跳检测失败,尝试重连') + this.reconnect() - this.timeoutTimer = setTimeout(() => { - console.warn('WebSocket重连超时') - this.close() - }, this.reconnectTimeout) - } - }, this.heartbeatInterval) - } + this.timeoutTimer = setTimeout(() => { + console.warn('WebSocket重连超时') + this.close() + }, this.reconnectTimeout) + } + }, this.heartbeatInterval) + } - // 重置心跳检测 - private resetHeartbeat(): void { - this.clearTimer('detectionTimer') - this.clearTimer('timeoutTimer') - this.startHeartbeat() - } + // 重置心跳检测 + private resetHeartbeat(): void { + this.clearTimer('detectionTimer') + this.clearTimer('timeoutTimer') + this.startHeartbeat() + } - // 开始发送ping消息 - private startPing(): void { - this.clearTimer('pingTimer') + // 开始发送ping消息 + private startPing(): void { + this.clearTimer('pingTimer') - this.pingTimer = setInterval(() => { - if (this.ws?.readyState !== WebSocket.OPEN) { - console.warn('WebSocket未连接,停止发送ping') - this.clearTimer('pingTimer') - this.reconnect() - return - } + this.pingTimer = setInterval(() => { + if (this.ws?.readyState !== WebSocket.OPEN) { + console.warn('WebSocket未连接,停止发送ping') + this.clearTimer('pingTimer') + this.reconnect() + return + } - try { - this.ws.send('ping') - console.log('发送ping消息') - } catch (error) { - console.error('发送ping消息失败:', error) - this.clearTimer('pingTimer') - this.reconnect() - } - }, this.pingInterval) - } + try { + this.ws.send('ping') + console.log('发送ping消息') + } catch (error) { + console.error('发送ping消息失败:', error) + this.clearTimer('pingTimer') + this.reconnect() + } + }, this.pingInterval) + } - // 重连 - 增加重连次数限制 - private reconnect(): void { - if (this.stopReconnect || this.isConnecting) { - return - } + // 重连 - 增加重连次数限制 + private reconnect(): void { + if (this.stopReconnect || this.isConnecting) { + return + } - // 检查是否超过最大重连次数 - if (this.reconnectAttempts >= this.maxReconnectAttempts) { - console.error(`已达到最大重连次数(${this.maxReconnectAttempts}),停止重连`) - this.close(true) - return - } + // 检查是否超过最大重连次数 + if (this.reconnectAttempts >= this.maxReconnectAttempts) { + console.error(`已达到最大重连次数(${this.maxReconnectAttempts}),停止重连`) + this.close(true) + return + } - this.reconnectAttempts++ - this.stopReconnect = true - this.close(true) + this.reconnectAttempts++ + this.stopReconnect = true + this.close(true) - const delay = this.calculateReconnectDelay() - console.log( - `将在${delay / 1000}秒后尝试重新连接(第${this.reconnectAttempts}/${this.maxReconnectAttempts}次)` - ) + const delay = this.calculateReconnectDelay() + console.log( + `将在${delay / 1000}秒后尝试重新连接(第${this.reconnectAttempts}/${this.maxReconnectAttempts}次)` + ) - this.clearTimer('reconnectTimer') - this.reconnectTimer = setTimeout(() => { - console.log(`尝试重新连接WebSocket(第${this.reconnectAttempts}次)`) - this.init() - this.stopReconnect = false - }, delay) - } + this.clearTimer('reconnectTimer') + this.reconnectTimer = setTimeout(() => { + console.log(`尝试重新连接WebSocket(第${this.reconnectAttempts}次)`) + this.init() + this.stopReconnect = false + }, delay) + } - // 计算重连延迟 - 指数退避策略 - private calculateReconnectDelay(): number { - // 基础延迟 + 随机值,避免多个客户端同时重连 - const jitter = Math.random() * 1000 // 0-1秒的随机延迟 - const baseDelay = Math.min( - this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1), - this.reconnectInterval * 5 - ) - return baseDelay + jitter - } + // 计算重连延迟 - 指数退避策略 + private calculateReconnectDelay(): number { + // 基础延迟 + 随机值,避免多个客户端同时重连 + const jitter = Math.random() * 1000 // 0-1秒的随机延迟 + const baseDelay = Math.min( + this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1), + this.reconnectInterval * 5 + ) + return baseDelay + jitter + } - // 清除指定定时器 - private clearTimer( - timerName: - | 'detectionTimer' - | 'timeoutTimer' - | 'reconnectTimer' - | 'pingTimer' - | 'connectionTimer' - ): void { - if (this[timerName]) { - clearTimeout(this[timerName] as NodeJS.Timeout) - this[timerName] = null - } - } + // 清除指定定时器 + private clearTimer( + timerName: + | 'detectionTimer' + | 'timeoutTimer' + | 'reconnectTimer' + | 'pingTimer' + | 'connectionTimer' + ): void { + if (this[timerName]) { + clearTimeout(this[timerName] as NodeJS.Timeout) + this[timerName] = null + } + } - // 清除所有定时器 - private clearAllTimers(): void { - this.clearTimer('detectionTimer') - this.clearTimer('timeoutTimer') - this.clearTimer('reconnectTimer') - this.clearTimer('pingTimer') - this.clearTimer('connectionTimer') - } + // 清除所有定时器 + private clearAllTimers(): void { + this.clearTimer('detectionTimer') + this.clearTimer('timeoutTimer') + this.clearTimer('reconnectTimer') + this.clearTimer('pingTimer') + this.clearTimer('connectionTimer') + } - // 获取当前连接状态 - get isWebSocketConnected(): boolean { - return this.isConnected - } + // 获取当前连接状态 + get isWebSocketConnected(): boolean { + return this.isConnected + } - // 获取当前连接状态文本 - get connectionStatusText(): string { - if (this.isConnecting) return '正在连接' - if (this.isConnected) return '已连接' - if (this.reconnectAttempts > 0 && !this.stopReconnect) - return `重连中(${this.reconnectAttempts}/${this.maxReconnectAttempts})` - return '已断开' - } + // 获取当前连接状态文本 + get connectionStatusText(): string { + if (this.isConnecting) return '正在连接' + if (this.isConnected) return '已连接' + if (this.reconnectAttempts > 0 && !this.stopReconnect) + return `重连中(${this.reconnectAttempts}/${this.maxReconnectAttempts})` + return '已断开' + } - // 销毁实例 - static destroyInstance(): void { - if (WebSocketClient.instance) { - WebSocketClient.instance.close() - WebSocketClient.instance = null - } - } + // 销毁实例 + static destroyInstance(): void { + if (WebSocketClient.instance) { + WebSocketClient.instance.close() + WebSocketClient.instance = null + } + } } diff --git a/src/utils/storage/storage-config.ts b/src/utils/storage/storage-config.ts index c41b60b..4f8fc10 100644 --- a/src/utils/storage/storage-config.ts +++ b/src/utils/storage/storage-config.ts @@ -27,96 +27,96 @@ * @author Art Design Pro Team */ export class StorageConfig { - /** 当前应用版本 */ - static readonly CURRENT_VERSION = __APP_VERSION__ + /** 当前应用版本 */ + static readonly CURRENT_VERSION = __APP_VERSION__ - /** 存储键前缀 */ - static readonly STORAGE_PREFIX = 'sys-v' + /** 存储键前缀 */ + static readonly STORAGE_PREFIX = 'sys-v' - /** 版本键名 */ - static readonly VERSION_KEY = 'sys-version' + /** 版本键名 */ + static readonly VERSION_KEY = 'sys-version' - /** 主题键名(index.html中使用了,如果修改,需要同步修改) */ - static readonly THEME_KEY = 'sys-theme' + /** 主题键名(index.html中使用了,如果修改,需要同步修改) */ + static readonly THEME_KEY = 'sys-theme' - /** 上次登录用户ID键名(用于判断是否为同一用户登录) */ - static readonly LAST_USER_ID_KEY = 'sys-last-user-id' + /** 上次登录用户ID键名(用于判断是否为同一用户登录) */ + static readonly LAST_USER_ID_KEY = 'sys-last-user-id' - /** 跳过升级检查的版本 */ - static readonly SKIP_UPGRADE_VERSION = '1.0.0' + /** 跳过升级检查的版本 */ + static readonly SKIP_UPGRADE_VERSION = '1.0.0' - /** 升级处理延迟时间(毫秒) */ - static readonly UPGRADE_DELAY = 1000 + /** 升级处理延迟时间(毫秒) */ + static readonly UPGRADE_DELAY = 1000 - /** 登出延迟时间(毫秒) */ - static readonly LOGOUT_DELAY = 1000 + /** 登出延迟时间(毫秒) */ + static readonly LOGOUT_DELAY = 1000 - /** - * 生成版本化的存储键名 - * @param storeId 存储ID - * @param version 版本号,默认使用当前版本 - */ - static generateStorageKey(storeId: string, version: string = this.CURRENT_VERSION): string { - return `${this.STORAGE_PREFIX}${version}-${storeId}` - } + /** + * 生成版本化的存储键名 + * @param storeId 存储ID + * @param version 版本号,默认使用当前版本 + */ + static generateStorageKey(storeId: string, version: string = this.CURRENT_VERSION): string { + return `${this.STORAGE_PREFIX}${version}-${storeId}` + } - /** - * 生成旧版本的存储键名(不带分隔符) - * @param version 版本号,默认使用当前版本 - */ - static generateLegacyKey(version: string = this.CURRENT_VERSION): string { - return `${this.STORAGE_PREFIX}${version}` - } + /** + * 生成旧版本的存储键名(不带分隔符) + * @param version 版本号,默认使用当前版本 + */ + static generateLegacyKey(version: string = this.CURRENT_VERSION): string { + return `${this.STORAGE_PREFIX}${version}` + } - /** - * 创建存储键匹配的正则表达式 - * @param storeId 存储ID - */ - static createKeyPattern(storeId: string): RegExp { - return new RegExp(`^${this.STORAGE_PREFIX}[^-]+-${storeId}$`) - } + /** + * 创建存储键匹配的正则表达式 + * @param storeId 存储ID + */ + static createKeyPattern(storeId: string): RegExp { + return new RegExp(`^${this.STORAGE_PREFIX}[^-]+-${storeId}$`) + } - /** - * 创建当前版本存储键匹配的正则表达式 - */ - static createCurrentVersionPattern(): RegExp { - return new RegExp(`^${this.STORAGE_PREFIX}${this.CURRENT_VERSION}-`) - } + /** + * 创建当前版本存储键匹配的正则表达式 + */ + static createCurrentVersionPattern(): RegExp { + return new RegExp(`^${this.STORAGE_PREFIX}${this.CURRENT_VERSION}-`) + } - /** - * 创建任意版本存储键匹配的正则表达式 - */ - static createVersionPattern(): RegExp { - return new RegExp(`^${this.STORAGE_PREFIX}`) - } + /** + * 创建任意版本存储键匹配的正则表达式 + */ + static createVersionPattern(): RegExp { + return new RegExp(`^${this.STORAGE_PREFIX}`) + } - /** - * 检查是否为当前版本的键 - */ - static isCurrentVersionKey(key: string): boolean { - return key.startsWith(`${this.STORAGE_PREFIX}${this.CURRENT_VERSION}`) - } + /** + * 检查是否为当前版本的键 + */ + static isCurrentVersionKey(key: string): boolean { + return key.startsWith(`${this.STORAGE_PREFIX}${this.CURRENT_VERSION}`) + } - /** - * 检查是否为版本化的键 - */ - static isVersionedKey(key: string): boolean { - return key.startsWith(this.STORAGE_PREFIX) - } + /** + * 检查是否为版本化的键 + */ + static isVersionedKey(key: string): boolean { + return key.startsWith(this.STORAGE_PREFIX) + } - /** - * 从存储键中提取版本号 - */ - static extractVersionFromKey(key: string): string | null { - const match = key.match(new RegExp(`^${this.STORAGE_PREFIX}([^-]+)`)) - return match ? match[1] : null - } + /** + * 从存储键中提取版本号 + */ + static extractVersionFromKey(key: string): string | null { + const match = key.match(new RegExp(`^${this.STORAGE_PREFIX}([^-]+)`)) + return match ? match[1] : null + } - /** - * 从存储键中提取存储ID - */ - static extractStoreIdFromKey(key: string): string | null { - const match = key.match(new RegExp(`^${this.STORAGE_PREFIX}[^-]+-(.+)$`)) - return match ? match[1] : null - } + /** + * 从存储键中提取存储ID + */ + static extractStoreIdFromKey(key: string): string | null { + const match = key.match(new RegExp(`^${this.STORAGE_PREFIX}[^-]+-(.+)$`)) + return match ? match[1] : null + } } diff --git a/src/utils/storage/storage-key-manager.ts b/src/utils/storage/storage-key-manager.ts index ba14f65..b06f120 100644 --- a/src/utils/storage/storage-key-manager.ts +++ b/src/utils/storage/storage-key-manager.ts @@ -36,62 +36,62 @@ import { StorageConfig } from '@/utils/storage' * 负责处理版本化的存储键名生成和数据迁移 */ export class StorageKeyManager { - /** - * 获取当前版本的存储键名 - */ - private getCurrentVersionKey(storeId: string): string { - return StorageConfig.generateStorageKey(storeId) - } + /** + * 获取当前版本的存储键名 + */ + private getCurrentVersionKey(storeId: string): string { + return StorageConfig.generateStorageKey(storeId) + } - /** - * 检查当前版本的数据是否存在 - */ - private hasCurrentVersionData(key: string): boolean { - return localStorage.getItem(key) !== null - } + /** + * 检查当前版本的数据是否存在 + */ + private hasCurrentVersionData(key: string): boolean { + return localStorage.getItem(key) !== null + } - /** - * 查找其他版本的同名存储键 - */ - private findExistingKey(storeId: string): string | null { - const storageKeys = Object.keys(localStorage) - const pattern = StorageConfig.createKeyPattern(storeId) + /** + * 查找其他版本的同名存储键 + */ + private findExistingKey(storeId: string): string | null { + const storageKeys = Object.keys(localStorage) + const pattern = StorageConfig.createKeyPattern(storeId) - return storageKeys.find((key) => pattern.test(key) && localStorage.getItem(key)) || null - } + return storageKeys.find((key) => pattern.test(key) && localStorage.getItem(key)) || null + } - /** - * 将数据从旧版本迁移到当前版本 - */ - private migrateData(fromKey: string, toKey: string): void { - try { - const existingData = localStorage.getItem(fromKey) - if (existingData) { - localStorage.setItem(toKey, existingData) - console.info(`[Storage] 已迁移数据: ${fromKey} → ${toKey}`) - } - } catch (error) { - console.warn(`[Storage] 数据迁移失败: ${fromKey}`, error) - } - } + /** + * 将数据从旧版本迁移到当前版本 + */ + private migrateData(fromKey: string, toKey: string): void { + try { + const existingData = localStorage.getItem(fromKey) + if (existingData) { + localStorage.setItem(toKey, existingData) + console.info(`[Storage] 已迁移数据: ${fromKey} → ${toKey}`) + } + } catch (error) { + console.warn(`[Storage] 数据迁移失败: ${fromKey}`, error) + } + } - /** - * 获取持久化存储的键名(支持自动数据迁移) - */ - getStorageKey(storeId: string): string { - const currentKey = this.getCurrentVersionKey(storeId) + /** + * 获取持久化存储的键名(支持自动数据迁移) + */ + getStorageKey(storeId: string): string { + const currentKey = this.getCurrentVersionKey(storeId) - // 优先使用当前版本的数据 - if (this.hasCurrentVersionData(currentKey)) { - return currentKey - } + // 优先使用当前版本的数据 + if (this.hasCurrentVersionData(currentKey)) { + return currentKey + } - // 查找并迁移其他版本的数据 - const existingKey = this.findExistingKey(storeId) - if (existingKey) { - this.migrateData(existingKey, currentKey) - } + // 查找并迁移其他版本的数据 + const existingKey = this.findExistingKey(storeId) + if (existingKey) { + this.migrateData(existingKey, currentKey) + } - return currentKey - } + return currentKey + } } diff --git a/src/utils/storage/storage.ts b/src/utils/storage/storage.ts index 67b9e9e..d7aaf35 100644 --- a/src/utils/storage/storage.ts +++ b/src/utils/storage/storage.ts @@ -41,179 +41,181 @@ import { StorageConfig } from '@/utils/storage/storage-config' * 负责处理不同版本间的存储兼容性检查和数据验证 */ class StorageCompatibilityManager { - /** - * 获取系统版本号 - */ - getSystemVersion(): string | null { - return localStorage.getItem(StorageConfig.VERSION_KEY) - } + /** + * 获取系统版本号 + */ + getSystemVersion(): string | null { + return localStorage.getItem(StorageConfig.VERSION_KEY) + } - /** - * 获取系统存储数据(兼容旧格式) - */ - getSystemStorage(): any { - const version = this.getSystemVersion() || StorageConfig.CURRENT_VERSION - const legacyKey = StorageConfig.generateLegacyKey(version) - const data = localStorage.getItem(legacyKey) - return data ? JSON.parse(data) : null - } + /** + * 获取系统存储数据(兼容旧格式) + */ + getSystemStorage(): any { + const version = this.getSystemVersion() || StorageConfig.CURRENT_VERSION + const legacyKey = StorageConfig.generateLegacyKey(version) + const data = localStorage.getItem(legacyKey) + return data ? JSON.parse(data) : null + } - /** - * 检查当前版本是否有存储数据 - */ - private hasCurrentVersionStorage(): boolean { - const storageKeys = Object.keys(localStorage) - const currentVersionPattern = StorageConfig.createCurrentVersionPattern() + /** + * 检查当前版本是否有存储数据 + */ + private hasCurrentVersionStorage(): boolean { + const storageKeys = Object.keys(localStorage) + const currentVersionPattern = StorageConfig.createCurrentVersionPattern() - return storageKeys.some( - (key) => currentVersionPattern.test(key) && localStorage.getItem(key) !== null - ) - } + return storageKeys.some( + (key) => currentVersionPattern.test(key) && localStorage.getItem(key) !== null + ) + } - /** - * 检查是否存在任何版本的存储数据 - */ - private hasAnyVersionStorage(): boolean { - const storageKeys = Object.keys(localStorage) - const versionPattern = StorageConfig.createVersionPattern() + /** + * 检查是否存在任何版本的存储数据 + */ + private hasAnyVersionStorage(): boolean { + const storageKeys = Object.keys(localStorage) + const versionPattern = StorageConfig.createVersionPattern() - return storageKeys.some((key) => versionPattern.test(key) && localStorage.getItem(key) !== null) - } + return storageKeys.some( + (key) => versionPattern.test(key) && localStorage.getItem(key) !== null + ) + } - /** - * 获取旧格式的本地存储数据 - */ - private getLegacyStorageData(): Record { - try { - const systemStorage = this.getSystemStorage() - return systemStorage || {} - } catch (error) { - console.warn('[Storage] 解析旧格式存储数据失败:', error) - return {} - } - } + /** + * 获取旧格式的本地存储数据 + */ + private getLegacyStorageData(): Record { + try { + const systemStorage = this.getSystemStorage() + return systemStorage || {} + } catch (error) { + console.warn('[Storage] 解析旧格式存储数据失败:', error) + return {} + } + } - /** - * 显示存储错误消息 - */ - private showStorageError(): void { - ElMessage({ - type: 'error', - offset: 40, - duration: 5000, - message: '系统检测到本地数据异常,请重新登录系统恢复使用!' - }) - } + /** + * 显示存储错误消息 + */ + private showStorageError(): void { + ElMessage({ + type: 'error', + offset: 40, + duration: 5000, + message: '系统检测到本地数据异常,请重新登录系统恢复使用!' + }) + } - /** - * 执行系统登出 - */ - private performSystemLogout(): void { - setTimeout(() => { - try { - localStorage.clear() - useUserStore().logOut() - router.push({ name: 'Login' }) - console.info('[Storage] 已执行系统登出') - } catch (error) { - console.error('[Storage] 系统登出失败:', error) - } - }, StorageConfig.LOGOUT_DELAY) - } + /** + * 执行系统登出 + */ + private performSystemLogout(): void { + setTimeout(() => { + try { + localStorage.clear() + useUserStore().logOut() + router.push({ name: 'Login' }) + console.info('[Storage] 已执行系统登出') + } catch (error) { + console.error('[Storage] 系统登出失败:', error) + } + }, StorageConfig.LOGOUT_DELAY) + } - /** - * 处理存储异常 - */ - private handleStorageError(): void { - this.showStorageError() - this.performSystemLogout() - } + /** + * 处理存储异常 + */ + private handleStorageError(): void { + this.showStorageError() + this.performSystemLogout() + } - /** - * 验证存储数据完整性 - * @param requireAuth 是否需要验证登录状态(默认 false) - */ - validateStorageData(requireAuth: boolean = false): boolean { - try { - // 优先检查新版本存储结构 - if (this.hasCurrentVersionStorage()) { - // console.debug('[Storage] 发现当前版本存储数据') - return true - } + /** + * 验证存储数据完整性 + * @param requireAuth 是否需要验证登录状态(默认 false) + */ + validateStorageData(requireAuth: boolean = false): boolean { + try { + // 优先检查新版本存储结构 + if (this.hasCurrentVersionStorage()) { + // console.debug('[Storage] 发现当前版本存储数据') + return true + } - // 检查是否有任何版本的存储数据 - if (this.hasAnyVersionStorage()) { - // console.debug('[Storage] 发现其他版本存储数据,可能需要迁移') - return true - } + // 检查是否有任何版本的存储数据 + if (this.hasAnyVersionStorage()) { + // console.debug('[Storage] 发现其他版本存储数据,可能需要迁移') + return true + } - // 检查旧版本存储结构 - const legacyData = this.getLegacyStorageData() - if (Object.keys(legacyData).length === 0) { - // 只有在需要验证登录状态时才执行登出操作 - if (requireAuth) { - console.warn('[Storage] 未发现任何存储数据,需要重新登录') - this.performSystemLogout() - return false - } - // 首次访问或访问静态路由,不需要登出 - // console.debug('[Storage] 未发现存储数据,首次访问或访问静态路由') - return true - } + // 检查旧版本存储结构 + const legacyData = this.getLegacyStorageData() + if (Object.keys(legacyData).length === 0) { + // 只有在需要验证登录状态时才执行登出操作 + if (requireAuth) { + console.warn('[Storage] 未发现任何存储数据,需要重新登录') + this.performSystemLogout() + return false + } + // 首次访问或访问静态路由,不需要登出 + // console.debug('[Storage] 未发现存储数据,首次访问或访问静态路由') + return true + } - console.debug('[Storage] 发现旧版本存储数据') - return true - } catch (error) { - console.error('[Storage] 存储数据验证失败:', error) - // 只有在需要验证登录状态时才处理错误 - if (requireAuth) { - this.handleStorageError() - return false - } - return true - } - } + console.debug('[Storage] 发现旧版本存储数据') + return true + } catch (error) { + console.error('[Storage] 存储数据验证失败:', error) + // 只有在需要验证登录状态时才处理错误 + if (requireAuth) { + this.handleStorageError() + return false + } + return true + } + } - /** - * 检查存储是否为空 - */ - isStorageEmpty(): boolean { - // 检查新版本存储结构 - if (this.hasCurrentVersionStorage()) { - return false - } + /** + * 检查存储是否为空 + */ + isStorageEmpty(): boolean { + // 检查新版本存储结构 + if (this.hasCurrentVersionStorage()) { + return false + } - // 检查是否有任何版本的存储数据 - if (this.hasAnyVersionStorage()) { - return false - } + // 检查是否有任何版本的存储数据 + if (this.hasAnyVersionStorage()) { + return false + } - // 检查旧版本存储结构 - const legacyData = this.getLegacyStorageData() - return Object.keys(legacyData).length === 0 - } + // 检查旧版本存储结构 + const legacyData = this.getLegacyStorageData() + return Object.keys(legacyData).length === 0 + } - /** - * 检查存储兼容性 - * @param requireAuth 是否需要验证登录状态(默认 false) - */ - checkCompatibility(requireAuth: boolean = false): boolean { - try { - const isValid = this.validateStorageData(requireAuth) - const isEmpty = this.isStorageEmpty() + /** + * 检查存储兼容性 + * @param requireAuth 是否需要验证登录状态(默认 false) + */ + checkCompatibility(requireAuth: boolean = false): boolean { + try { + const isValid = this.validateStorageData(requireAuth) + const isEmpty = this.isStorageEmpty() - if (isValid || isEmpty) { - // console.debug('[Storage] 存储兼容性检查通过') - return true - } + if (isValid || isEmpty) { + // console.debug('[Storage] 存储兼容性检查通过') + return true + } - console.warn('[Storage] 存储兼容性检查失败') - return false - } catch (error) { - console.error('[Storage] 兼容性检查异常:', error) - return false - } - } + console.warn('[Storage] 存储兼容性检查失败') + return false + } catch (error) { + console.error('[Storage] 兼容性检查异常:', error) + return false + } + } } // 创建存储兼容性管理器实例 @@ -223,14 +225,14 @@ const storageManager = new StorageCompatibilityManager() * 获取系统存储数据 */ export function getSystemStorage(): any { - return storageManager.getSystemStorage() + return storageManager.getSystemStorage() } /** * 获取系统版本号 */ export function getSysVersion(): string | null { - return storageManager.getSystemVersion() + return storageManager.getSystemVersion() } /** @@ -238,7 +240,7 @@ export function getSysVersion(): string | null { * @param requireAuth 是否需要验证登录状态(默认 false) */ export function validateStorageData(requireAuth: boolean = false): boolean { - return storageManager.validateStorageData(requireAuth) + return storageManager.validateStorageData(requireAuth) } /** @@ -246,5 +248,5 @@ export function validateStorageData(requireAuth: boolean = false): boolean { * @param requireAuth 是否需要验证登录状态(默认 false) */ export function checkStorageCompatibility(requireAuth: boolean = false): boolean { - return storageManager.checkCompatibility(requireAuth) + return storageManager.checkCompatibility(requireAuth) } diff --git a/src/utils/sys/error-handle.ts b/src/utils/sys/error-handle.ts index 22109c2..c871200 100644 --- a/src/utils/sys/error-handle.ts +++ b/src/utils/sys/error-handle.ts @@ -35,68 +35,70 @@ import type { App } from 'vue' * Vue 运行时错误处理 */ export function vueErrorHandler(err: unknown, instance: any, info: string) { - console.error('[VueError]', err, info, instance) - // 这里可以上报到服务端,比如: - // reportError({ type: 'vue', err, info }) + console.error('[VueError]', err, info, instance) + // 这里可以上报到服务端,比如: + // reportError({ type: 'vue', err, info }) } /** * 全局脚本错误处理 */ export function scriptErrorHandler( - message: Event | string, - source?: string, - lineno?: number, - colno?: number, - error?: Error + message: Event | string, + source?: string, + lineno?: number, + colno?: number, + error?: Error ): boolean { - console.error('[ScriptError]', { message, source, lineno, colno, error }) - // reportError({ type: 'script', message, source, lineno, colno, error }) - return true // 阻止默认控制台报错,可根据需求改 + console.error('[ScriptError]', { message, source, lineno, colno, error }) + // reportError({ type: 'script', message, source, lineno, colno, error }) + return true // 阻止默认控制台报错,可根据需求改 } /** * Promise 未捕获错误处理 */ export function registerPromiseErrorHandler() { - window.addEventListener('unhandledrejection', (event) => { - console.error('[PromiseError]', event.reason) - // reportError({ type: 'promise', reason: event.reason }) - }) + window.addEventListener('unhandledrejection', (event) => { + console.error('[PromiseError]', event.reason) + // reportError({ type: 'promise', reason: event.reason }) + }) } /** * 资源加载错误处理 (img, script, css...) */ export function registerResourceErrorHandler() { - window.addEventListener( - 'error', - (event: Event) => { - const target = event.target as HTMLElement - if ( - target && - (target.tagName === 'IMG' || target.tagName === 'SCRIPT' || target.tagName === 'LINK') - ) { - console.error('[ResourceError]', { - tagName: target.tagName, - src: - (target as HTMLImageElement).src || - (target as HTMLScriptElement).src || - (target as HTMLLinkElement).href - }) - // reportError({ type: 'resource', target }) - } - }, - true // 捕获阶段才能监听到资源错误 - ) + window.addEventListener( + 'error', + (event: Event) => { + const target = event.target as HTMLElement + if ( + target && + (target.tagName === 'IMG' || + target.tagName === 'SCRIPT' || + target.tagName === 'LINK') + ) { + console.error('[ResourceError]', { + tagName: target.tagName, + src: + (target as HTMLImageElement).src || + (target as HTMLScriptElement).src || + (target as HTMLLinkElement).href + }) + // reportError({ type: 'resource', target }) + } + }, + true // 捕获阶段才能监听到资源错误 + ) } /** * 安装统一错误处理 */ export function setupErrorHandle(app: App) { - app.config.errorHandler = vueErrorHandler - window.onerror = scriptErrorHandler - registerPromiseErrorHandler() - registerResourceErrorHandler() + app.config.errorHandler = vueErrorHandler + window.onerror = scriptErrorHandler + registerPromiseErrorHandler() + registerResourceErrorHandler() } diff --git a/src/utils/sys/mittBus.ts b/src/utils/sys/mittBus.ts index 22f0108..430e5b8 100644 --- a/src/utils/sys/mittBus.ts +++ b/src/utils/sys/mittBus.ts @@ -45,16 +45,16 @@ import mitt, { type Emitter } from 'mitt' // 定义事件类型映射 type Events = { - // 烟花效果事件 - 可选的图片URL参数 - triggerFireworks: string | undefined - // 打开设置面板事件 - 无参数 - openSetting: void - // 打开搜索对话框事件 - 无参数 - openSearchDialog: void - // 打开聊天窗口事件 - 无参数 - openChat: void - // 打开锁屏事件 - 无参数 - openLockScreen: void + // 烟花效果事件 - 可选的图片URL参数 + triggerFireworks: string | undefined + // 打开设置面板事件 - 无参数 + openSetting: void + // 打开搜索对话框事件 - 无参数 + openSearchDialog: void + // 打开聊天窗口事件 - 无参数 + openChat: void + // 打开锁屏事件 - 无参数 + openLockScreen: void } // 创建类型安全的事件总线实例 diff --git a/src/utils/sys/upgrade.ts b/src/utils/sys/upgrade.ts index 53d3465..46752bf 100644 --- a/src/utils/sys/upgrade.ts +++ b/src/utils/sys/upgrade.ts @@ -44,226 +44,230 @@ import { StorageConfig } from '@/utils/storage/storage-config' * 负责处理版本比较、升级检测和数据清理 */ class VersionManager { - /** - * 规范化版本号字符串,移除前缀 'v' - */ - private normalizeVersion(version: string): string { - return version.replace(/^v/, '') - } + /** + * 规范化版本号字符串,移除前缀 'v' + */ + private normalizeVersion(version: string): string { + return version.replace(/^v/, '') + } - /** - * 获取存储的版本号 - */ - private getStoredVersion(): string | null { - return localStorage.getItem(StorageConfig.VERSION_KEY) - } + /** + * 获取存储的版本号 + */ + private getStoredVersion(): string | null { + return localStorage.getItem(StorageConfig.VERSION_KEY) + } - /** - * 设置版本号到存储 - */ - private setStoredVersion(version: string): void { - localStorage.setItem(StorageConfig.VERSION_KEY, version) - } + /** + * 设置版本号到存储 + */ + private setStoredVersion(version: string): void { + localStorage.setItem(StorageConfig.VERSION_KEY, version) + } - /** - * 检查是否应该跳过升级处理 - */ - private shouldSkipUpgrade(): boolean { - return StorageConfig.CURRENT_VERSION === StorageConfig.SKIP_UPGRADE_VERSION - } + /** + * 检查是否应该跳过升级处理 + */ + private shouldSkipUpgrade(): boolean { + return StorageConfig.CURRENT_VERSION === StorageConfig.SKIP_UPGRADE_VERSION + } - /** - * 检查是否为首次访问 - */ - private isFirstVisit(storedVersion: string | null): boolean { - return !storedVersion - } + /** + * 检查是否为首次访问 + */ + private isFirstVisit(storedVersion: string | null): boolean { + return !storedVersion + } - /** - * 检查版本是否相同 - */ - private isSameVersion(storedVersion: string): boolean { - return storedVersion === StorageConfig.CURRENT_VERSION - } + /** + * 检查版本是否相同 + */ + private isSameVersion(storedVersion: string): boolean { + return storedVersion === StorageConfig.CURRENT_VERSION + } - /** - * 查找旧的存储结构 - */ - private findLegacyStorage(): { oldSysKey: string | null; oldVersionKeys: string[] } { - const storageKeys = Object.keys(localStorage) - const currentVersionPrefix = StorageConfig.generateStorageKey('').slice(0, -1) // 移除末尾的 '-' + /** + * 查找旧的存储结构 + */ + private findLegacyStorage(): { oldSysKey: string | null; oldVersionKeys: string[] } { + const storageKeys = Object.keys(localStorage) + const currentVersionPrefix = StorageConfig.generateStorageKey('').slice(0, -1) // 移除末尾的 '-' - // 查找旧的单一存储结构 - const oldSysKey = - storageKeys.find( - (key) => - StorageConfig.isVersionedKey(key) && key !== currentVersionPrefix && !key.includes('-') - ) || null + // 查找旧的单一存储结构 + const oldSysKey = + storageKeys.find( + (key) => + StorageConfig.isVersionedKey(key) && + key !== currentVersionPrefix && + !key.includes('-') + ) || null - // 查找旧版本的分离存储键 - const oldVersionKeys = storageKeys.filter( - (key) => - StorageConfig.isVersionedKey(key) && - !StorageConfig.isCurrentVersionKey(key) && - key.includes('-') - ) + // 查找旧版本的分离存储键 + const oldVersionKeys = storageKeys.filter( + (key) => + StorageConfig.isVersionedKey(key) && + !StorageConfig.isCurrentVersionKey(key) && + key.includes('-') + ) - return { oldSysKey, oldVersionKeys } - } + return { oldSysKey, oldVersionKeys } + } - /** - * 检查是否需要重新登录 - */ - private shouldRequireReLogin(storedVersion: string): boolean { - const normalizedCurrent = this.normalizeVersion(StorageConfig.CURRENT_VERSION) - const normalizedStored = this.normalizeVersion(storedVersion) + /** + * 检查是否需要重新登录 + */ + private shouldRequireReLogin(storedVersion: string): boolean { + const normalizedCurrent = this.normalizeVersion(StorageConfig.CURRENT_VERSION) + const normalizedStored = this.normalizeVersion(storedVersion) - return upgradeLogList.value.some((item) => { - const itemVersion = this.normalizeVersion(item.version) - return ( - item.requireReLogin && itemVersion > normalizedStored && itemVersion <= normalizedCurrent - ) - }) - } + return upgradeLogList.value.some((item) => { + const itemVersion = this.normalizeVersion(item.version) + return ( + item.requireReLogin && + itemVersion > normalizedStored && + itemVersion <= normalizedCurrent + ) + }) + } - /** - * 构建升级通知消息 - */ - private buildUpgradeMessage(requireReLogin: boolean): string { - const { title: content } = upgradeLogList.value[0] + /** + * 构建升级通知消息 + */ + private buildUpgradeMessage(requireReLogin: boolean): string { + const { title: content } = upgradeLogList.value[0] - const messageParts = [ - `

`, - `系统已升级到 ${StorageConfig.CURRENT_VERSION} 版本,此次更新带来了以下改进:`, - `

`, - content - ] + const messageParts = [ + `

`, + `系统已升级到 ${StorageConfig.CURRENT_VERSION} 版本,此次更新带来了以下改进:`, + `

`, + content + ] - if (requireReLogin) { - messageParts.push( - `

升级完成,请重新登录后继续使用。

` - ) - } + if (requireReLogin) { + messageParts.push( + `

升级完成,请重新登录后继续使用。

` + ) + } - return messageParts.join('') - } + return messageParts.join('') + } - /** - * 显示升级通知 - */ - private showUpgradeNotification(message: string): void { - ElNotification({ - title: '系统升级公告', - message, - duration: 0, - type: 'success', - dangerouslyUseHTMLString: true - }) - } + /** + * 显示升级通知 + */ + private showUpgradeNotification(message: string): void { + ElNotification({ + title: '系统升级公告', + message, + duration: 0, + type: 'success', + dangerouslyUseHTMLString: true + }) + } - /** - * 清理旧版本数据 - */ - private cleanupLegacyData(oldSysKey: string | null, oldVersionKeys: string[]): void { - // 清理旧的单一存储结构 - if (oldSysKey) { - localStorage.removeItem(oldSysKey) - console.info(`[Upgrade] 已清理旧存储: ${oldSysKey}`) - } + /** + * 清理旧版本数据 + */ + private cleanupLegacyData(oldSysKey: string | null, oldVersionKeys: string[]): void { + // 清理旧的单一存储结构 + if (oldSysKey) { + localStorage.removeItem(oldSysKey) + console.info(`[Upgrade] 已清理旧存储: ${oldSysKey}`) + } - // 清理旧版本的分离存储 - oldVersionKeys.forEach((key) => { - localStorage.removeItem(key) - console.info(`[Upgrade] 已清理旧存储: ${key}`) - }) - } + // 清理旧版本的分离存储 + oldVersionKeys.forEach((key) => { + localStorage.removeItem(key) + console.info(`[Upgrade] 已清理旧存储: ${key}`) + }) + } - /** - * 执行升级后的登出操作 - */ - private performLogout(): void { - try { - useUserStore().logOut() - console.info('[Upgrade] 已执行升级后登出') - } catch (error) { - console.error('[Upgrade] 升级后登出失败:', error) - } - } + /** + * 执行升级后的登出操作 + */ + private performLogout(): void { + try { + useUserStore().logOut() + console.info('[Upgrade] 已执行升级后登出') + } catch (error) { + console.error('[Upgrade] 升级后登出失败:', error) + } + } - /** - * 执行升级流程 - */ - private async executeUpgrade( - storedVersion: string, - legacyStorage: ReturnType - ): Promise { - try { - if (!upgradeLogList.value.length) { - console.warn('[Upgrade] 升级日志列表为空') - return - } + /** + * 执行升级流程 + */ + private async executeUpgrade( + storedVersion: string, + legacyStorage: ReturnType + ): Promise { + try { + if (!upgradeLogList.value.length) { + console.warn('[Upgrade] 升级日志列表为空') + return + } - const requireReLogin = this.shouldRequireReLogin(storedVersion) - const message = this.buildUpgradeMessage(requireReLogin) + const requireReLogin = this.shouldRequireReLogin(storedVersion) + const message = this.buildUpgradeMessage(requireReLogin) - // 显示升级通知 - this.showUpgradeNotification(message) + // 显示升级通知 + this.showUpgradeNotification(message) - // 更新版本号 - this.setStoredVersion(StorageConfig.CURRENT_VERSION) + // 更新版本号 + this.setStoredVersion(StorageConfig.CURRENT_VERSION) - // 清理旧数据 - this.cleanupLegacyData(legacyStorage.oldSysKey, legacyStorage.oldVersionKeys) + // 清理旧数据 + this.cleanupLegacyData(legacyStorage.oldSysKey, legacyStorage.oldVersionKeys) - // 执行登出(如果需要) - if (requireReLogin) { - this.performLogout() - } + // 执行登出(如果需要) + if (requireReLogin) { + this.performLogout() + } - console.info(`[Upgrade] 升级完成: ${storedVersion} → ${StorageConfig.CURRENT_VERSION}`) - } catch (error) { - console.error('[Upgrade] 系统升级处理失败:', error) - } - } + console.info(`[Upgrade] 升级完成: ${storedVersion} → ${StorageConfig.CURRENT_VERSION}`) + } catch (error) { + console.error('[Upgrade] 系统升级处理失败:', error) + } + } - /** - * 系统升级处理主流程 - */ - async processUpgrade(): Promise { - // 跳过特定版本 - if (this.shouldSkipUpgrade()) { - console.debug('[Upgrade] 跳过版本升级检查') - return - } + /** + * 系统升级处理主流程 + */ + async processUpgrade(): Promise { + // 跳过特定版本 + if (this.shouldSkipUpgrade()) { + console.debug('[Upgrade] 跳过版本升级检查') + return + } - const storedVersion = this.getStoredVersion() + const storedVersion = this.getStoredVersion() - // 首次访问处理 - if (this.isFirstVisit(storedVersion)) { - this.setStoredVersion(StorageConfig.CURRENT_VERSION) - // console.info('[Upgrade] 首次访问,已设置当前版本') - return - } + // 首次访问处理 + if (this.isFirstVisit(storedVersion)) { + this.setStoredVersion(StorageConfig.CURRENT_VERSION) + // console.info('[Upgrade] 首次访问,已设置当前版本') + return + } - // 版本相同,无需升级 - if (this.isSameVersion(storedVersion!)) { - // console.debug('[Upgrade] 版本相同,无需升级') - return - } + // 版本相同,无需升级 + if (this.isSameVersion(storedVersion!)) { + // console.debug('[Upgrade] 版本相同,无需升级') + return + } - // 检查是否有需要升级的旧数据 - const legacyStorage = this.findLegacyStorage() - if (!legacyStorage.oldSysKey && legacyStorage.oldVersionKeys.length === 0) { - this.setStoredVersion(StorageConfig.CURRENT_VERSION) - console.info('[Upgrade] 无旧数据,已更新版本号') - return - } + // 检查是否有需要升级的旧数据 + const legacyStorage = this.findLegacyStorage() + if (!legacyStorage.oldSysKey && legacyStorage.oldVersionKeys.length === 0) { + this.setStoredVersion(StorageConfig.CURRENT_VERSION) + console.info('[Upgrade] 无旧数据,已更新版本号') + return + } - // 延迟执行升级流程,确保应用已完全加载 - setTimeout(() => { - this.executeUpgrade(storedVersion!, legacyStorage) - }, StorageConfig.UPGRADE_DELAY) - } + // 延迟执行升级流程,确保应用已完全加载 + setTimeout(() => { + this.executeUpgrade(storedVersion!, legacyStorage) + }, StorageConfig.UPGRADE_DELAY) + } } // 创建版本管理器实例 @@ -273,5 +277,5 @@ const versionManager = new VersionManager() * 系统升级处理入口函数 */ export async function systemUpgrade(): Promise { - await versionManager.processUpgrade() + await versionManager.processUpgrade() } diff --git a/src/utils/table/tableCache.ts b/src/utils/table/tableCache.ts index 045a7ce..1bd4d79 100644 --- a/src/utils/table/tableCache.ts +++ b/src/utils/table/tableCache.ts @@ -36,231 +36,231 @@ import { hash } from 'ohash' // 缓存失效策略枚举 export enum CacheInvalidationStrategy { - /** 清空所有缓存 */ - CLEAR_ALL = 'clear_all', - /** 仅清空当前查询条件的缓存 */ - CLEAR_CURRENT = 'clear_current', - /** 清空所有分页缓存(保留不同搜索条件的缓存) */ - CLEAR_PAGINATION = 'clear_pagination', - /** 不清除缓存 */ - KEEP_ALL = 'keep_all' + /** 清空所有缓存 */ + CLEAR_ALL = 'clear_all', + /** 仅清空当前查询条件的缓存 */ + CLEAR_CURRENT = 'clear_current', + /** 清空所有分页缓存(保留不同搜索条件的缓存) */ + CLEAR_PAGINATION = 'clear_pagination', + /** 不清除缓存 */ + KEEP_ALL = 'keep_all' } // 通用 API 响应接口(兼容不同的后端响应格式) export interface ApiResponse { - records?: T[] - data?: T[] - total?: number - current?: number - size?: number - [key: string]: unknown + records?: T[] + data?: T[] + total?: number + current?: number + size?: number + [key: string]: unknown } // 缓存存储接口 export interface CacheItem { - data: T[] - response: ApiResponse - timestamp: number - params: string - // 缓存标签,用于分组管理 - tags: Set - // 访问次数(用于 LRU 算法) - accessCount: number - // 最后访问时间 - lastAccessTime: number + data: T[] + response: ApiResponse + timestamp: number + params: string + // 缓存标签,用于分组管理 + tags: Set + // 访问次数(用于 LRU 算法) + accessCount: number + // 最后访问时间 + lastAccessTime: number } // 增强的缓存管理类 export class TableCache { - private cache = new Map>() - private cacheTime: number - private maxSize: number - private enableLog: boolean + private cache = new Map>() + private cacheTime: number + private maxSize: number + private enableLog: boolean - constructor(cacheTime = 5 * 60 * 1000, maxSize = 50, enableLog = false) { - // 默认5分钟,最多50条缓存 - this.cacheTime = cacheTime - this.maxSize = maxSize - this.enableLog = enableLog - } + constructor(cacheTime = 5 * 60 * 1000, maxSize = 50, enableLog = false) { + // 默认5分钟,最多50条缓存 + this.cacheTime = cacheTime + this.maxSize = maxSize + this.enableLog = enableLog + } - // 内部日志工具 - private log(message: string, ...args: any[]) { - if (this.enableLog) { - console.log(`[TableCache] ${message}`, ...args) - } - } + // 内部日志工具 + private log(message: string, ...args: any[]) { + if (this.enableLog) { + console.log(`[TableCache] ${message}`, ...args) + } + } - // 生成稳定的缓存键 - private generateKey(params: unknown): string { - return hash(params) - } + // 生成稳定的缓存键 + private generateKey(params: unknown): string { + return hash(params) + } - // 🔧 优化:增强类型安全性 - private generateTags(params: Record): Set { - const tags = new Set() + // 🔧 优化:增强类型安全性 + private generateTags(params: Record): Set { + const tags = new Set() - // 添加搜索条件标签 - const searchKeys = Object.keys(params).filter( - (key) => - !['current', 'size', 'total'].includes(key) && - params[key] !== undefined && - params[key] !== '' && - params[key] !== null - ) + // 添加搜索条件标签 + const searchKeys = Object.keys(params).filter( + (key) => + !['current', 'size', 'total'].includes(key) && + params[key] !== undefined && + params[key] !== '' && + params[key] !== null + ) - if (searchKeys.length > 0) { - const searchTag = searchKeys.map((key) => `${key}:${String(params[key])}`).join('|') - tags.add(`search:${searchTag}`) - } else { - tags.add('search:default') - } + if (searchKeys.length > 0) { + const searchTag = searchKeys.map((key) => `${key}:${String(params[key])}`).join('|') + tags.add(`search:${searchTag}`) + } else { + tags.add('search:default') + } - // 添加分页标签 - tags.add(`pagination:${params.size || 10}`) - // 添加通用分页标签,用于清理所有分页缓存 - tags.add('pagination') + // 添加分页标签 + tags.add(`pagination:${params.size || 10}`) + // 添加通用分页标签,用于清理所有分页缓存 + tags.add('pagination') - return tags - } + return tags + } - // 🔧 优化:LRU 缓存清理 - private evictLRU(): void { - if (this.cache.size <= this.maxSize) return + // 🔧 优化:LRU 缓存清理 + private evictLRU(): void { + if (this.cache.size <= this.maxSize) return - // 找到最少使用的缓存项 - let lruKey = '' - let minAccessCount = Infinity - let oldestTime = Infinity + // 找到最少使用的缓存项 + let lruKey = '' + let minAccessCount = Infinity + let oldestTime = Infinity - for (const [key, item] of this.cache.entries()) { - if ( - item.accessCount < minAccessCount || - (item.accessCount === minAccessCount && item.lastAccessTime < oldestTime) - ) { - lruKey = key - minAccessCount = item.accessCount - oldestTime = item.lastAccessTime - } - } + for (const [key, item] of this.cache.entries()) { + if ( + item.accessCount < minAccessCount || + (item.accessCount === minAccessCount && item.lastAccessTime < oldestTime) + ) { + lruKey = key + minAccessCount = item.accessCount + oldestTime = item.lastAccessTime + } + } - if (lruKey) { - this.cache.delete(lruKey) - this.log(`LRU 清理缓存: ${lruKey}`) - } - } + if (lruKey) { + this.cache.delete(lruKey) + this.log(`LRU 清理缓存: ${lruKey}`) + } + } - // 设置缓存 - set(params: unknown, data: T[], response: ApiResponse): void { - const key = this.generateKey(params) - const tags = this.generateTags(params as Record) - const now = Date.now() + // 设置缓存 + set(params: unknown, data: T[], response: ApiResponse): void { + const key = this.generateKey(params) + const tags = this.generateTags(params as Record) + const now = Date.now() - // 检查是否需要清理 - this.evictLRU() + // 检查是否需要清理 + this.evictLRU() - this.cache.set(key, { - data, - response, - timestamp: now, - params: key, - tags, - accessCount: 1, - lastAccessTime: now - }) - } + this.cache.set(key, { + data, + response, + timestamp: now, + params: key, + tags, + accessCount: 1, + lastAccessTime: now + }) + } - // 获取缓存 - get(params: unknown): CacheItem | null { - const key = this.generateKey(params) - const item = this.cache.get(key) + // 获取缓存 + get(params: unknown): CacheItem | null { + const key = this.generateKey(params) + const item = this.cache.get(key) - if (!item) return null + if (!item) return null - // 检查是否过期 - if (Date.now() - item.timestamp > this.cacheTime) { - this.cache.delete(key) - return null - } + // 检查是否过期 + if (Date.now() - item.timestamp > this.cacheTime) { + this.cache.delete(key) + return null + } - // 更新访问统计 - item.accessCount++ - item.lastAccessTime = Date.now() + // 更新访问统计 + item.accessCount++ + item.lastAccessTime = Date.now() - return item - } + return item + } - // 根据标签清除缓存 - clearByTags(tags: string[]): number { - let clearedCount = 0 + // 根据标签清除缓存 + clearByTags(tags: string[]): number { + let clearedCount = 0 - for (const [key, item] of this.cache.entries()) { - // 检查是否包含任意一个标签 - const hasMatchingTag = tags.some((tag) => - Array.from(item.tags).some((itemTag) => itemTag.includes(tag)) - ) + for (const [key, item] of this.cache.entries()) { + // 检查是否包含任意一个标签 + const hasMatchingTag = tags.some((tag) => + Array.from(item.tags).some((itemTag) => itemTag.includes(tag)) + ) - if (hasMatchingTag) { - this.cache.delete(key) - clearedCount++ - } - } + if (hasMatchingTag) { + this.cache.delete(key) + clearedCount++ + } + } - return clearedCount - } + return clearedCount + } - // 清除当前搜索条件的缓存 - clearCurrentSearch(params: unknown): number { - const key = this.generateKey(params) - const deleted = this.cache.delete(key) - return deleted ? 1 : 0 - } + // 清除当前搜索条件的缓存 + clearCurrentSearch(params: unknown): number { + const key = this.generateKey(params) + const deleted = this.cache.delete(key) + return deleted ? 1 : 0 + } - // 清除分页缓存 - clearPagination(): number { - return this.clearByTags(['pagination']) - } + // 清除分页缓存 + clearPagination(): number { + return this.clearByTags(['pagination']) + } - // 清空所有缓存 - clear(): void { - this.cache.clear() - } + // 清空所有缓存 + clear(): void { + this.cache.clear() + } - // 获取缓存统计信息 - getStats(): { total: number; size: string; hitRate: string } { - const total = this.cache.size - let totalSize = 0 - let totalAccess = 0 + // 获取缓存统计信息 + getStats(): { total: number; size: string; hitRate: string } { + const total = this.cache.size + let totalSize = 0 + let totalAccess = 0 - for (const item of this.cache.values()) { - // 粗略估算大小(JSON字符串长度) - totalSize += JSON.stringify(item.data).length - totalAccess += item.accessCount - } + for (const item of this.cache.values()) { + // 粗略估算大小(JSON字符串长度) + totalSize += JSON.stringify(item.data).length + totalAccess += item.accessCount + } - // 转换为人类可读的大小 - const sizeInKB = (totalSize / 1024).toFixed(2) - const avgHits = total > 0 ? (totalAccess / total).toFixed(1) : '0' + // 转换为人类可读的大小 + const sizeInKB = (totalSize / 1024).toFixed(2) + const avgHits = total > 0 ? (totalAccess / total).toFixed(1) : '0' - return { - total, - size: `${sizeInKB}KB`, - hitRate: `${avgHits} avg hits` - } - } + return { + total, + size: `${sizeInKB}KB`, + hitRate: `${avgHits} avg hits` + } + } - // 清理过期缓存 - cleanupExpired(): number { - let cleanedCount = 0 - const now = Date.now() + // 清理过期缓存 + cleanupExpired(): number { + let cleanedCount = 0 + const now = Date.now() - for (const [key, item] of this.cache.entries()) { - if (now - item.timestamp > this.cacheTime) { - this.cache.delete(key) - cleanedCount++ - } - } + for (const [key, item] of this.cache.entries()) { + if (now - item.timestamp > this.cacheTime) { + this.cache.delete(key) + cleanedCount++ + } + } - return cleanedCount - } + return cleanedCount + } } diff --git a/src/utils/table/tableConfig.ts b/src/utils/table/tableConfig.ts index e464c89..59c12fb 100644 --- a/src/utils/table/tableConfig.ts +++ b/src/utils/table/tableConfig.ts @@ -34,22 +34,22 @@ * @author Art Design Pro Team */ export const tableConfig = { - // 响应数据字段映射配置,系统会从接口返回数据中按顺序查找这些字段 - // 列表数据 - recordFields: ['list', 'data', 'records', 'items', 'result', 'rows'], - // 总条数 - totalFields: ['total', 'count'], - // 当前页码 - currentFields: ['current', 'page', 'pageNum'], - // 每页大小 - sizeFields: ['size', 'pageSize', 'limit'], + // 响应数据字段映射配置,系统会从接口返回数据中按顺序查找这些字段 + // 列表数据 + recordFields: ['list', 'data', 'records', 'items', 'result', 'rows'], + // 总条数 + totalFields: ['total', 'count'], + // 当前页码 + currentFields: ['current', 'page', 'pageNum'], + // 每页大小 + sizeFields: ['size', 'pageSize', 'limit'], - // 请求参数映射配置,前端发送请求时使用的分页参数名 - // useTable 组合式函数传递分页参数的时候 用 current 跟 size - paginationKey: { - // 当前页码 - current: 'current', - // 每页大小 - size: 'size' - } + // 请求参数映射配置,前端发送请求时使用的分页参数名 + // useTable 组合式函数传递分页参数的时候 用 current 跟 size + paginationKey: { + // 当前页码 + current: 'current', + // 每页大小 + size: 'size' + } } diff --git a/src/utils/table/tableUtils.ts b/src/utils/table/tableUtils.ts index 3ca9db1..2e2f8fa 100644 --- a/src/utils/table/tableUtils.ts +++ b/src/utils/table/tableUtils.ts @@ -44,254 +44,254 @@ import { tableConfig } from './tableConfig' // 请求参数基础接口,扩展分页参数 export interface BaseRequestParams extends Api.Common.PaginationParams { - [key: string]: unknown + [key: string]: unknown } // 错误处理接口 export interface TableError { - code: string - message: string - details?: unknown + code: string + message: string + details?: unknown } // 辅助函数:从对象中提取记录数组 function extractRecords(obj: Record, fields: string[]): T[] { - for (const field of fields) { - if (field in obj && Array.isArray(obj[field])) { - return obj[field] as T[] - } - } - return [] + for (const field of fields) { + if (field in obj && Array.isArray(obj[field])) { + return obj[field] as T[] + } + } + return [] } // 辅助函数:从对象中提取总数 function extractTotal(obj: Record, records: unknown[], fields: string[]): number { - for (const field of fields) { - if (field in obj && typeof obj[field] === 'number') { - return obj[field] as number - } - } - return records.length + for (const field of fields) { + if (field in obj && typeof obj[field] === 'number') { + return obj[field] as number + } + } + return records.length } // 辅助函数:提取分页参数 function extractPagination( - obj: Record, - data?: Record + obj: Record, + data?: Record ): Pick, 'current' | 'size'> | undefined { - const result: Partial, 'current' | 'size'>> = {} - const sources = [obj, data ?? {}] + const result: Partial, 'current' | 'size'>> = {} + const sources = [obj, data ?? {}] - const currentFields = tableConfig.currentFields - for (const src of sources) { - for (const field of currentFields) { - if (field in src && typeof src[field] === 'number') { - result.current = src[field] as number - break - } - } - if (result.current !== undefined) break - } + const currentFields = tableConfig.currentFields + for (const src of sources) { + for (const field of currentFields) { + if (field in src && typeof src[field] === 'number') { + result.current = src[field] as number + break + } + } + if (result.current !== undefined) break + } - const sizeFields = tableConfig.sizeFields - for (const src of sources) { - for (const field of sizeFields) { - if (field in src && typeof src[field] === 'number') { - result.size = src[field] as number - break - } - } - if (result.size !== undefined) break - } + const sizeFields = tableConfig.sizeFields + for (const src of sources) { + for (const field of sizeFields) { + if (field in src && typeof src[field] === 'number') { + result.size = src[field] as number + break + } + } + if (result.size !== undefined) break + } - if (result.current === undefined && result.size === undefined) return undefined - return result + if (result.current === undefined && result.size === undefined) return undefined + return result } /** * 默认响应适配器 - 支持多种常见的API响应格式 */ export const defaultResponseAdapter = (response: unknown): ApiResponse => { - // 定义支持的字段 - const recordFields = tableConfig.recordFields + // 定义支持的字段 + const recordFields = tableConfig.recordFields - if (!response) { - return { records: [], total: 0 } - } + if (!response) { + return { records: [], total: 0 } + } - if (Array.isArray(response)) { - return { records: response, total: response.length } - } + if (Array.isArray(response)) { + return { records: response, total: response.length } + } - if (typeof response !== 'object') { - console.warn( - '[tableUtils] 无法识别的响应格式,支持的格式包括: 数组、包含' + - recordFields.join('/') + - '字段的对象、嵌套data对象。当前格式:', - response - ) - return { records: [], total: 0 } - } + if (typeof response !== 'object') { + console.warn( + '[tableUtils] 无法识别的响应格式,支持的格式包括: 数组、包含' + + recordFields.join('/') + + '字段的对象、嵌套data对象。当前格式:', + response + ) + return { records: [], total: 0 } + } - const res = response as Record - let records: T[] = [] - let total = 0 - let pagination: Pick, 'current' | 'size'> | undefined + const res = response as Record + let records: T[] = [] + let total = 0 + let pagination: Pick, 'current' | 'size'> | undefined - // 处理标准格式或直接列表 - records = extractRecords(res, recordFields) - total = extractTotal(res, records, tableConfig.totalFields) - pagination = extractPagination(res) + // 处理标准格式或直接列表 + records = extractRecords(res, recordFields) + total = extractTotal(res, records, tableConfig.totalFields) + pagination = extractPagination(res) - // 如果没有找到,检查嵌套data - if (records.length === 0 && 'data' in res && typeof res.data === 'object') { - const data = res.data as Record - records = extractRecords(data, ['list', 'records', 'items']) - total = extractTotal(data, records, tableConfig.totalFields) - pagination = extractPagination(res, data) + // 如果没有找到,检查嵌套data + if (records.length === 0 && 'data' in res && typeof res.data === 'object') { + const data = res.data as Record + records = extractRecords(data, ['list', 'records', 'items']) + total = extractTotal(data, records, tableConfig.totalFields) + pagination = extractPagination(res, data) - if (Array.isArray(res.data)) { - records = res.data as T[] - total = records.length - } - } + if (Array.isArray(res.data)) { + records = res.data as T[] + total = records.length + } + } - if (!recordFields.some((field) => field in res) && records.length === 0) { - console.warn('[tableUtils] 无法识别的响应格式') - console.warn('支持的字段包括: ' + recordFields.join('、'), response) - console.warn('扩展字段请到 utils/table/tableConfig 文件配置') - } + if (!recordFields.some((field) => field in res) && records.length === 0) { + console.warn('[tableUtils] 无法识别的响应格式') + console.warn('支持的字段包括: ' + recordFields.join('、'), response) + console.warn('扩展字段请到 utils/table/tableConfig 文件配置') + } - const result: ApiResponse = { records, total } - if (pagination) { - Object.assign(result, pagination) - } - return result + const result: ApiResponse = { records, total } + if (pagination) { + Object.assign(result, pagination) + } + return result } /** * 从标准化的API响应中提取表格数据 */ export const extractTableData = (response: ApiResponse): T[] => { - const data = response.records || response.data || [] - return Array.isArray(data) ? data : [] + const data = response.records || response.data || [] + return Array.isArray(data) ? data : [] } /** * 根据API响应更新分页信息 */ export const updatePaginationFromResponse = ( - pagination: Api.Common.PaginationParams, - response: ApiResponse + pagination: Api.Common.PaginationParams, + response: ApiResponse ): void => { - pagination.total = response.total ?? pagination.total ?? 0 + pagination.total = response.total ?? pagination.total ?? 0 - if (response.current !== undefined) { - pagination.current = response.current - } + if (response.current !== undefined) { + pagination.current = response.current + } - const maxPage = Math.max(1, Math.ceil(pagination.total / (pagination.size || 1))) - if (pagination.current > maxPage) { - pagination.current = maxPage - } + const maxPage = Math.max(1, Math.ceil(pagination.total / (pagination.size || 1))) + if (pagination.current > maxPage) { + pagination.current = maxPage + } } /** * 创建智能防抖函数 - 支持取消和立即执行 */ export const createSmartDebounce = Promise>( - fn: T, - delay: number + fn: T, + delay: number ): T & { cancel: () => void; flush: () => Promise } => { - let timeoutId: NodeJS.Timeout | null = null - let lastArgs: Parameters | null = null - let lastResolve: ((value: any) => void) | null = null - let lastReject: ((reason: any) => void) | null = null + let timeoutId: NodeJS.Timeout | null = null + let lastArgs: Parameters | null = null + let lastResolve: ((value: any) => void) | null = null + let lastReject: ((reason: any) => void) | null = null - const debouncedFn = (...args: Parameters): Promise => { - return new Promise((resolve, reject) => { - if (timeoutId) clearTimeout(timeoutId) - lastArgs = args - lastResolve = resolve - lastReject = reject - timeoutId = setTimeout(async () => { - try { - const result = await fn(...args) - resolve(result) - } catch (error) { - reject(error) - } finally { - timeoutId = null - lastArgs = null - lastResolve = null - lastReject = null - } - }, delay) - }) - } + const debouncedFn = (...args: Parameters): Promise => { + return new Promise((resolve, reject) => { + if (timeoutId) clearTimeout(timeoutId) + lastArgs = args + lastResolve = resolve + lastReject = reject + timeoutId = setTimeout(async () => { + try { + const result = await fn(...args) + resolve(result) + } catch (error) { + reject(error) + } finally { + timeoutId = null + lastArgs = null + lastResolve = null + lastReject = null + } + }, delay) + }) + } - debouncedFn.cancel = () => { - if (timeoutId) clearTimeout(timeoutId) - timeoutId = null - lastArgs = null - lastResolve = null - lastReject = null - } + debouncedFn.cancel = () => { + if (timeoutId) clearTimeout(timeoutId) + timeoutId = null + lastArgs = null + lastResolve = null + lastReject = null + } - debouncedFn.flush = async () => { - if (timeoutId && lastArgs && lastResolve && lastReject) { - clearTimeout(timeoutId) - timeoutId = null - const args = lastArgs - const resolve = lastResolve - const reject = lastReject - lastArgs = null - lastResolve = null - lastReject = null - try { - const result = await fn(...args) - resolve(result) - return result - } catch (error) { - reject(error) - throw error - } - } - return Promise.resolve() - } + debouncedFn.flush = async () => { + if (timeoutId && lastArgs && lastResolve && lastReject) { + clearTimeout(timeoutId) + timeoutId = null + const args = lastArgs + const resolve = lastResolve + const reject = lastReject + lastArgs = null + lastResolve = null + lastReject = null + try { + const result = await fn(...args) + resolve(result) + return result + } catch (error) { + reject(error) + throw error + } + } + return Promise.resolve() + } - return debouncedFn as any + return debouncedFn as any } /** * 生成错误处理函数 */ export const createErrorHandler = ( - onError?: (error: TableError) => void, - enableLog: boolean = false + onError?: (error: TableError) => void, + enableLog: boolean = false ) => { - const logger = { - error: (message: string, ...args: any[]) => { - if (enableLog) console.error(`[useTable] ${message}`, ...args) - } - } + const logger = { + error: (message: string, ...args: any[]) => { + if (enableLog) console.error(`[useTable] ${message}`, ...args) + } + } - return (err: unknown, context: string): TableError => { - const tableError: TableError = { - code: 'UNKNOWN_ERROR', - message: '未知错误', - details: err - } + return (err: unknown, context: string): TableError => { + const tableError: TableError = { + code: 'UNKNOWN_ERROR', + message: '未知错误', + details: err + } - if (err instanceof Error) { - tableError.message = err.message - tableError.code = err.name - } else if (typeof err === 'string') { - tableError.message = err - } + if (err instanceof Error) { + tableError.message = err.message + tableError.code = err.name + } else if (typeof err === 'string') { + tableError.message = err + } - logger.error(`${context}:`, err) - onError?.(tableError) - return tableError - } + logger.error(`${context}:`, err) + onError?.(tableError) + return tableError + } } diff --git a/src/utils/ui/animation.ts b/src/utils/ui/animation.ts index 5efd02a..3b2c0af 100644 --- a/src/utils/ui/animation.ts +++ b/src/utils/ui/animation.ts @@ -38,29 +38,29 @@ const { LIGHT, DARK } = SystemThemeEnum * @param e 鼠标点击事件 */ export const themeAnimation = (e: any) => { - const x = e.clientX - const y = e.clientY - // 计算鼠标点击位置距离视窗的最大圆半径 - const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y)) + const x = e.clientX + const y = e.clientY + // 计算鼠标点击位置距离视窗的最大圆半径 + const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y)) - // 设置CSS变量 - document.documentElement.style.setProperty('--x', x + 'px') - document.documentElement.style.setProperty('--y', y + 'px') - document.documentElement.style.setProperty('--r', endRadius + 'px') + // 设置CSS变量 + document.documentElement.style.setProperty('--x', x + 'px') + document.documentElement.style.setProperty('--y', y + 'px') + document.documentElement.style.setProperty('--r', endRadius + 'px') - if (document.startViewTransition) { - document.startViewTransition(() => toggleTheme()) - } else { - toggleTheme() - } + if (document.startViewTransition) { + document.startViewTransition(() => toggleTheme()) + } else { + toggleTheme() + } } /** * 切换主题 */ const toggleTheme = () => { - useTheme().switchThemeStyles(useSettingStore().systemThemeType === LIGHT ? DARK : LIGHT) - useCommon().refresh() + useTheme().switchThemeStyles(useSettingStore().systemThemeType === LIGHT ? DARK : LIGHT) + useCommon().refresh() } /** @@ -68,13 +68,13 @@ const toggleTheme = () => { * @param enable 是否启用过渡效果 */ export const toggleTransition = (enable: boolean) => { - const body = document.body + const body = document.body - if (enable) { - body.classList.add('theme-change') - } else { - setTimeout(() => { - body.classList.remove('theme-change') - }, 300) - } + if (enable) { + body.classList.add('theme-change') + } else { + setTimeout(() => { + body.classList.remove('theme-change') + }, 300) + } } diff --git a/src/utils/ui/colors.ts b/src/utils/ui/colors.ts index b4f6b77..f92bca1 100644 --- a/src/utils/ui/colors.ts +++ b/src/utils/ui/colors.ts @@ -47,10 +47,10 @@ import { useSettingStore } from '@/store/modules/setting' * 颜色转换结果接口 */ interface RgbaResult { - red: number - green: number - blue: number - rgba: string + red: number + green: number + blue: number + rgba: string } /** @@ -59,7 +59,7 @@ interface RgbaResult { * @returns CSS变量值 */ export function getCssVar(name: string): string { - return getComputedStyle(document.documentElement).getPropertyValue(name) + return getComputedStyle(document.documentElement).getPropertyValue(name) } /** @@ -68,8 +68,8 @@ export function getCssVar(name: string): string { * @returns 是否为有效的hex颜色 */ function isValidHexColor(hex: string): boolean { - const cleanHex = hex.trim().replace(/^#/, '') - return /^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleanHex) + const cleanHex = hex.trim().replace(/^#/, '') + return /^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(cleanHex) } /** @@ -80,8 +80,8 @@ function isValidHexColor(hex: string): boolean { * @returns 是否为有效的RGB值 */ function isValidRgbValue(r: number, g: number, b: number): boolean { - const isValid = (value: number) => Number.isInteger(value) && value >= 0 && value <= 255 - return isValid(r) && isValid(g) && isValid(b) + const isValid = (value: number) => Number.isInteger(value) && value >= 0 && value <= 255 + return isValid(r) && isValid(g) && isValid(b) } /** @@ -91,31 +91,31 @@ function isValidRgbValue(r: number, g: number, b: number): boolean { * @returns 包含RGB值和RGBA字符串的对象 */ export function hexToRgba(hex: string, opacity: number): RgbaResult { - if (!isValidHexColor(hex)) { - throw new Error('Invalid hex color format') - } + if (!isValidHexColor(hex)) { + throw new Error('Invalid hex color format') + } - // 移除可能存在的 # 前缀并转换为大写 - let cleanHex = hex.trim().replace(/^#/, '').toUpperCase() + // 移除可能存在的 # 前缀并转换为大写 + let cleanHex = hex.trim().replace(/^#/, '').toUpperCase() - // 如果是缩写形式(如 FFF),转换为完整形式 - if (cleanHex.length === 3) { - cleanHex = cleanHex - .split('') - .map((char) => char.repeat(2)) - .join('') - } + // 如果是缩写形式(如 FFF),转换为完整形式 + if (cleanHex.length === 3) { + cleanHex = cleanHex + .split('') + .map((char) => char.repeat(2)) + .join('') + } - // 解析 RGB 值 - const [red, green, blue] = cleanHex.match(/\w\w/g)!.map((x) => parseInt(x, 16)) + // 解析 RGB 值 + const [red, green, blue] = cleanHex.match(/\w\w/g)!.map((x) => parseInt(x, 16)) - // 确保 opacity 在有效范围内 - const validOpacity = Math.max(0, Math.min(1, opacity)) + // 确保 opacity 在有效范围内 + const validOpacity = Math.max(0, Math.min(1, opacity)) - // 构建 RGBA 字符串 - const rgba = `rgba(${red}, ${green}, ${blue}, ${validOpacity.toFixed(2)})` + // 构建 RGBA 字符串 + const rgba = `rgba(${red}, ${green}, ${blue}, ${validOpacity.toFixed(2)})` - return { red, green, blue, rgba } + return { red, green, blue, rgba } } /** @@ -124,28 +124,28 @@ export function hexToRgba(hex: string, opacity: number): RgbaResult { * @returns RGB数组 [r, g, b] */ export function hexToRgb(hexColor: string): number[] { - if (!isValidHexColor(hexColor)) { - ElMessage.warning('输入错误的hex颜色值') - throw new Error('Invalid hex color format') - } + if (!isValidHexColor(hexColor)) { + ElMessage.warning('输入错误的hex颜色值') + throw new Error('Invalid hex color format') + } - const cleanHex = hexColor.replace(/^#/, '') - let hex = cleanHex + const cleanHex = hexColor.replace(/^#/, '') + let hex = cleanHex - // 处理缩写形式 - if (hex.length === 3) { - hex = hex - .split('') - .map((char) => char.repeat(2)) - .join('') - } + // 处理缩写形式 + if (hex.length === 3) { + hex = hex + .split('') + .map((char) => char.repeat(2)) + .join('') + } - const hexPairs = hex.match(/../g) - if (!hexPairs) { - throw new Error('Invalid hex color format') - } + const hexPairs = hex.match(/../g) + if (!hexPairs) { + throw new Error('Invalid hex color format') + } - return hexPairs.map((hexPair) => parseInt(hexPair, 16)) + return hexPairs.map((hexPair) => parseInt(hexPair, 16)) } /** @@ -156,17 +156,17 @@ export function hexToRgb(hexColor: string): number[] { * @returns hex颜色值 */ export function rgbToHex(r: number, g: number, b: number): string { - if (!isValidRgbValue(r, g, b)) { - ElMessage.warning('输入错误的RGB颜色值') - throw new Error('Invalid RGB color values') - } + if (!isValidRgbValue(r, g, b)) { + ElMessage.warning('输入错误的RGB颜色值') + throw new Error('Invalid RGB color values') + } - const toHex = (value: number) => { - const hex = value.toString(16) - return hex.length === 1 ? `0${hex}` : hex - } + const toHex = (value: number) => { + const hex = value.toString(16) + return hex.length === 1 ? `0${hex}` : hex + } - return `#${toHex(r)}${toHex(g)}${toHex(b)}` + return `#${toHex(r)}${toHex(g)}${toHex(b)}` } /** @@ -177,17 +177,17 @@ export function rgbToHex(r: number, g: number, b: number): string { * @returns 混合后的颜色 */ export function colourBlend(color1: string, color2: string, ratio: number): string { - const validRatio = Math.max(0, Math.min(1, Number(ratio))) + const validRatio = Math.max(0, Math.min(1, Number(ratio))) - const rgb1 = hexToRgb(color1) - const rgb2 = hexToRgb(color2) + const rgb1 = hexToRgb(color1) + const rgb2 = hexToRgb(color2) - const blendedRgb = rgb1.map((value1, index) => { - const value2 = rgb2[index] - return Math.round(value1 * (1 - validRatio) + value2 * validRatio) - }) + const blendedRgb = rgb1.map((value1, index) => { + const value2 = rgb2[index] + return Math.round(value1 * (1 - validRatio) + value2 * validRatio) + }) - return rgbToHex(blendedRgb[0], blendedRgb[1], blendedRgb[2]) + return rgbToHex(blendedRgb[0], blendedRgb[1], blendedRgb[2]) } /** @@ -198,19 +198,19 @@ export function colourBlend(color1: string, color2: string, ratio: number): stri * @returns 变浅后的颜色 */ export function getLightColor(color: string, level: number, isDark: boolean = false): string { - if (!isValidHexColor(color)) { - ElMessage.warning('输入错误的hex颜色值') - throw new Error('Invalid hex color format') - } + if (!isValidHexColor(color)) { + ElMessage.warning('输入错误的hex颜色值') + throw new Error('Invalid hex color format') + } - if (isDark) { - return getDarkColor(color, level) - } + if (isDark) { + return getDarkColor(color, level) + } - const rgb = hexToRgb(color) - const lightRgb = rgb.map((value) => Math.floor((255 - value) * level + value)) + const rgb = hexToRgb(color) + const lightRgb = rgb.map((value) => Math.floor((255 - value) * level + value)) - return rgbToHex(lightRgb[0], lightRgb[1], lightRgb[2]) + return rgbToHex(lightRgb[0], lightRgb[1], lightRgb[2]) } /** @@ -220,15 +220,15 @@ export function getLightColor(color: string, level: number, isDark: boolean = fa * @returns 变深后的颜色 */ export function getDarkColor(color: string, level: number): string { - if (!isValidHexColor(color)) { - ElMessage.warning('输入错误的hex颜色值') - throw new Error('Invalid hex color format') - } + if (!isValidHexColor(color)) { + ElMessage.warning('输入错误的hex颜色值') + throw new Error('Invalid hex color format') + } - const rgb = hexToRgb(color) - const darkRgb = rgb.map((value) => Math.floor(value * (1 - level))) + const rgb = hexToRgb(color) + const darkRgb = rgb.map((value) => Math.floor(value * (1 - level))) - return rgbToHex(darkRgb[0], darkRgb[1], darkRgb[2]) + return rgbToHex(darkRgb[0], darkRgb[1], darkRgb[2]) } /** @@ -237,21 +237,21 @@ export function getDarkColor(color: string, level: number): string { * @param isDark 是否为暗色主题 */ export function handleElementThemeColor(theme: string, isDark: boolean = false): void { - document.documentElement.style.setProperty('--el-color-primary', theme) + document.documentElement.style.setProperty('--el-color-primary', theme) - for (let i = 1; i <= 9; i++) { - document.documentElement.style.setProperty( - `--el-color-primary-light-${i}`, - getLightColor(theme, i / 10, isDark) - ) - } + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty( + `--el-color-primary-light-${i}`, + getLightColor(theme, i / 10, isDark) + ) + } - for (let i = 1; i <= 9; i++) { - document.documentElement.style.setProperty( - `--el-color-primary-dark-${i}`, - getDarkColor(theme, i / 10) - ) - } + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty( + `--el-color-primary-dark-${i}`, + getDarkColor(theme, i / 10) + ) + } } /** @@ -259,15 +259,15 @@ export function handleElementThemeColor(theme: string, isDark: boolean = false): * @param color 主题颜色 */ export function setElementThemeColor(color: string): void { - const mixColor = '#ffffff' - const elStyle = document.documentElement.style + const mixColor = '#ffffff' + const elStyle = document.documentElement.style - elStyle.setProperty('--el-color-primary', color) - handleElementThemeColor(color, useSettingStore().isDark) + elStyle.setProperty('--el-color-primary', color) + handleElementThemeColor(color, useSettingStore().isDark) - // 生成更淡一点的颜色 - for (let i = 1; i < 16; i++) { - const itemColor = colourBlend(color, mixColor, i / 16) - elStyle.setProperty(`--el-color-primary-custom-${i}`, itemColor) - } + // 生成更淡一点的颜色 + for (let i = 1; i < 16; i++) { + const itemColor = colourBlend(color, mixColor, i / 16) + elStyle.setProperty(`--el-color-primary-custom-${i}`, itemColor) + } } diff --git a/src/utils/ui/emojo.ts b/src/utils/ui/emojo.ts index cabad7d..41ef513 100644 --- a/src/utils/ui/emojo.ts +++ b/src/utils/ui/emojo.ts @@ -13,10 +13,10 @@ // macos 用户 按 shift + 6 可以唤出更多表情…… const EmojiText: { [key: string]: string } = { - '0': 'O_O', // 空 - '200': '^_^', // 成功 - '400': 'T_T', // 错误请求 - '500': 'X_X' // 服务器内部错误,无法完成请求 + '0': 'O_O', // 空 + '200': '^_^', // 成功 + '400': 'T_T', // 错误请求 + '500': 'X_X' // 服务器内部错误,无法完成请求 } // const EmojiIcon = ['🟢', '🔴', '🟡 ', '🚀', '✨', '💡', '🛠️', '🔥', '🎉', '🌟', '🌈'] diff --git a/src/utils/ui/loading.ts b/src/utils/ui/loading.ts index 6580e02..edb7b45 100644 --- a/src/utils/ui/loading.ts +++ b/src/utils/ui/loading.ts @@ -35,50 +35,50 @@ import { fourDotsSpinnerSvg } from '@/assets/svg/loading' * @returns 背景色字符串 */ const getLoadingBackground = (): string => { - const isDark = document.documentElement.classList.contains('dark') - return isDark ? 'rgba(7, 7, 7, 0.85)' : '#fff' + const isDark = document.documentElement.classList.contains('dark') + return isDark ? 'rgba(7, 7, 7, 0.85)' : '#fff' } const DEFAULT_LOADING_CONFIG = { - lock: true, - get background() { - return getLoadingBackground() - }, - svg: fourDotsSpinnerSvg, - svgViewBox: '0 0 40 40', - customClass: 'art-loading-fix' + lock: true, + get background() { + return getLoadingBackground() + }, + svg: fourDotsSpinnerSvg, + svgViewBox: '0 0 40 40', + customClass: 'art-loading-fix' } as const interface LoadingInstance { - close: () => void + close: () => void } let loadingInstance: LoadingInstance | null = null export const loadingService = { - /** - * 显示 loading - * @returns 关闭 loading 的函数 - */ - showLoading(): () => void { - if (!loadingInstance) { - // 每次显示时获取最新的配置,确保背景色与当前主题同步 - const config = { - ...DEFAULT_LOADING_CONFIG, - background: getLoadingBackground() - } - loadingInstance = ElLoading.service(config) - } - return () => this.hideLoading() - }, + /** + * 显示 loading + * @returns 关闭 loading 的函数 + */ + showLoading(): () => void { + if (!loadingInstance) { + // 每次显示时获取最新的配置,确保背景色与当前主题同步 + const config = { + ...DEFAULT_LOADING_CONFIG, + background: getLoadingBackground() + } + loadingInstance = ElLoading.service(config) + } + return () => this.hideLoading() + }, - /** - * 隐藏 loading - */ - hideLoading(): void { - if (loadingInstance) { - loadingInstance.close() - loadingInstance = null - } - } + /** + * 隐藏 loading + */ + hideLoading(): void { + if (loadingInstance) { + loadingInstance.close() + loadingInstance = null + } + } } diff --git a/src/utils/ui/tabs.ts b/src/utils/ui/tabs.ts index 5f53ea5..7f894cd 100644 --- a/src/utils/ui/tabs.ts +++ b/src/utils/ui/tabs.ts @@ -34,27 +34,27 @@ * @author Art Design Pro Team */ export const TAB_CONFIG = { - 'tab-default': { - openTop: 106, - closeTop: 60, - openHeight: 121, - closeHeight: 75 - }, - 'tab-card': { - openTop: 122, - closeTop: 78, - openHeight: 139, - closeHeight: 95 - }, - 'tab-google': { - openTop: 122, - closeTop: 78, - openHeight: 139, - closeHeight: 95 - } + 'tab-default': { + openTop: 106, + closeTop: 60, + openHeight: 121, + closeHeight: 75 + }, + 'tab-card': { + openTop: 122, + closeTop: 78, + openHeight: 139, + closeHeight: 95 + }, + 'tab-google': { + openTop: 122, + closeTop: 78, + openHeight: 139, + closeHeight: 95 + } } // 获取当前 tab 样式配置,设置默认值 export const getTabConfig = (style: string) => { - return TAB_CONFIG[style as keyof typeof TAB_CONFIG] || TAB_CONFIG['tab-card'] // 默认使用 tab-card 配置 + return TAB_CONFIG[style as keyof typeof TAB_CONFIG] || TAB_CONFIG['tab-card'] // 默认使用 tab-card 配置 } diff --git a/src/views/auth/forget-password/index.vue b/src/views/auth/forget-password/index.vue index 147259e..d9b9658 100644 --- a/src/views/auth/forget-password/index.vue +++ b/src/views/auth/forget-password/index.vue @@ -1,62 +1,62 @@ diff --git a/src/views/auth/login/index.vue b/src/views/auth/login/index.vue index 2a857d7..e10d3ec 100644 --- a/src/views/auth/login/index.vue +++ b/src/views/auth/login/index.vue @@ -1,284 +1,284 @@ diff --git a/src/views/auth/login/style.css b/src/views/auth/login/style.css index bd8c3a9..114a17a 100644 --- a/src/views/auth/login/style.css +++ b/src/views/auth/login/style.css @@ -2,37 +2,37 @@ /* 授权页右侧区域 */ .auth-right-wrap { - @apply absolute inset-0 w-[440px] h-[650px] py-[5px] m-auto overflow-hidden + @apply absolute inset-0 w-[440px] h-[650px] py-[5px] m-auto overflow-hidden max-sm:px-7 max-sm:w-full animate-[slideInRight_0.6s_cubic-bezier(0.25,0.46,0.45,0.94)_forwards] max-md:animate-none; - .form { - @apply h-full py-[40px]; - } + .form { + @apply h-full py-[40px]; + } - .title { - @apply text-g-900 text-4xl font-semibold max-md:text-3xl max-sm:pt-10; - } + .title { + @apply text-g-900 text-4xl font-semibold max-md:text-3xl max-sm:pt-10; + } - .sub-title { - @apply mt-[10px] text-g-600 text-sm; - } + .sub-title { + @apply mt-[10px] text-g-600 text-sm; + } - .custom-height { - @apply !h-[40px]; - } + .custom-height { + @apply !h-[40px]; + } } /* 滑入动画 */ @keyframes slideInRight { - from { - opacity: 0; - transform: translateX(30px); - } + from { + opacity: 0; + transform: translateX(30px); + } - to { - opacity: 1; - transform: translateX(0); - } + to { + opacity: 1; + transform: translateX(0); + } } diff --git a/src/views/auth/register/index.vue b/src/views/auth/register/index.vue index 9a8570d..e7e5a8d 100644 --- a/src/views/auth/register/index.vue +++ b/src/views/auth/register/index.vue @@ -1,240 +1,244 @@ diff --git a/src/views/dashboard/console/index.vue b/src/views/dashboard/console/index.vue index 154c330..0b55b5e 100644 --- a/src/views/dashboard/console/index.vue +++ b/src/views/dashboard/console/index.vue @@ -1,41 +1,41 @@ diff --git a/src/views/dashboard/console/modules/about-project.vue b/src/views/dashboard/console/modules/about-project.vue index ed946ce..67f3d81 100644 --- a/src/views/dashboard/console/modules/about-project.vue +++ b/src/views/dashboard/console/modules/about-project.vue @@ -1,44 +1,44 @@ diff --git a/src/views/dashboard/console/modules/active-user.vue b/src/views/dashboard/console/modules/active-user.vue index da740f2..93876bb 100644 --- a/src/views/dashboard/console/modules/active-user.vue +++ b/src/views/dashboard/console/modules/active-user.vue @@ -1,47 +1,49 @@ diff --git a/src/views/dashboard/console/modules/card-list.vue b/src/views/dashboard/console/modules/card-list.vue index 5fc76a7..ff430f7 100644 --- a/src/views/dashboard/console/modules/card-list.vue +++ b/src/views/dashboard/console/modules/card-list.vue @@ -1,74 +1,78 @@ diff --git a/src/views/dashboard/console/modules/dynamic-stats.vue b/src/views/dashboard/console/modules/dynamic-stats.vue index 1876950..d7644f6 100644 --- a/src/views/dashboard/console/modules/dynamic-stats.vue +++ b/src/views/dashboard/console/modules/dynamic-stats.vue @@ -1,79 +1,79 @@ diff --git a/src/views/dashboard/console/modules/new-user.vue b/src/views/dashboard/console/modules/new-user.vue index 9d39522..4a10ed7 100644 --- a/src/views/dashboard/console/modules/new-user.vue +++ b/src/views/dashboard/console/modules/new-user.vue @@ -1,169 +1,171 @@ diff --git a/src/views/dashboard/console/modules/sales-overview.vue b/src/views/dashboard/console/modules/sales-overview.vue index 32904b8..4654e07 100644 --- a/src/views/dashboard/console/modules/sales-overview.vue +++ b/src/views/dashboard/console/modules/sales-overview.vue @@ -1,43 +1,43 @@ diff --git a/src/views/dashboard/console/modules/todo-list.vue b/src/views/dashboard/console/modules/todo-list.vue index ab9a86c..d70fbb7 100644 --- a/src/views/dashboard/console/modules/todo-list.vue +++ b/src/views/dashboard/console/modules/todo-list.vue @@ -1,71 +1,71 @@ diff --git a/src/views/exception/403/index.vue b/src/views/exception/403/index.vue index 2756c42..8d75795 100644 --- a/src/views/exception/403/index.vue +++ b/src/views/exception/403/index.vue @@ -1,16 +1,16 @@ diff --git a/src/views/exception/404/index.vue b/src/views/exception/404/index.vue index 6b64f45..4e44bd2 100644 --- a/src/views/exception/404/index.vue +++ b/src/views/exception/404/index.vue @@ -1,16 +1,16 @@ diff --git a/src/views/exception/500/index.vue b/src/views/exception/500/index.vue index 1b26377..d23b44c 100644 --- a/src/views/exception/500/index.vue +++ b/src/views/exception/500/index.vue @@ -1,16 +1,16 @@ diff --git a/src/views/index/index.vue b/src/views/index/index.vue index 415a436..e528551 100644 --- a/src/views/index/index.vue +++ b/src/views/index/index.vue @@ -1,29 +1,29 @@ diff --git a/src/views/index/style.scss b/src/views/index/style.scss index c89f354..7203848 100644 --- a/src/views/index/style.scss +++ b/src/views/index/style.scss @@ -1,93 +1,93 @@ .app-layout { - display: flex; - width: 100%; - min-height: 100vh; - background: var(--default-bg-color); + display: flex; + width: 100%; + min-height: 100vh; + background: var(--default-bg-color); - #app-sidebar { - flex-shrink: 0; - } + #app-sidebar { + flex-shrink: 0; + } - #app-main { - display: flex; - flex: 1; - flex-direction: column; - min-width: 0; - height: 100vh; - overflow: auto; + #app-main { + display: flex; + flex: 1; + flex-direction: column; + min-width: 0; + height: 100vh; + overflow: auto; - #app-header { - position: sticky; - top: 0; - z-index: 50; - flex-shrink: 0; - width: 100%; - } + #app-header { + position: sticky; + top: 0; + z-index: 50; + flex-shrink: 0; + width: 100%; + } - #app-content { - flex: 1; + #app-content { + flex: 1; - :deep(.layout-content) { - box-sizing: border-box; - width: calc(100% - 40px); - margin: auto; + :deep(.layout-content) { + box-sizing: border-box; + width: calc(100% - 40px); + margin: auto; - // 子页面默认 style - .page-content { - position: relative; - box-sizing: border-box; - padding: 20px; - overflow: hidden; - background: var(--default-box-color); - border-radius: calc(var(--custom-radius) / 2 + 2px) !important; - } - } - } - } + // 子页面默认 style + .page-content { + position: relative; + box-sizing: border-box; + padding: 20px; + overflow: hidden; + background: var(--default-box-color); + border-radius: calc(var(--custom-radius) / 2 + 2px) !important; + } + } + } + } } @media only screen and (width <= 1180px) { - .app-layout { - #app-main { - height: 100dvh; - } - } + .app-layout { + #app-main { + height: 100dvh; + } + } } @media only screen and (width <= 800px) { - .app-layout { - position: relative; + .app-layout { + position: relative; - #app-sidebar { - position: fixed; - top: 0; - left: 0; - z-index: 300; - height: 100vh; - } + #app-sidebar { + position: fixed; + top: 0; + left: 0; + z-index: 300; + height: 100vh; + } - #app-main { - width: 100%; - height: auto; - overflow: visible; + #app-main { + width: 100%; + height: auto; + overflow: visible; - #app-content { - :deep(.layout-content) { - width: calc(100% - 40px); - } - } - } - } + #app-content { + :deep(.layout-content) { + width: calc(100% - 40px); + } + } + } + } } @media only screen and (width <= 640px) { - .app-layout { - #app-main { - #app-content { - :deep(.layout-content) { - width: calc(100% - 30px); - } - } - } - } + .app-layout { + #app-main { + #app-content { + :deep(.layout-content) { + width: calc(100% - 30px); + } + } + } + } } diff --git a/src/views/outside/Iframe.vue b/src/views/outside/Iframe.vue index 33ea0dc..75a915f 100644 --- a/src/views/outside/Iframe.vue +++ b/src/views/outside/Iframe.vue @@ -1,42 +1,42 @@ diff --git a/src/views/result/fail/index.vue b/src/views/result/fail/index.vue index 8fe2583..c1169d5 100644 --- a/src/views/result/fail/index.vue +++ b/src/views/result/fail/index.vue @@ -1,28 +1,28 @@ diff --git a/src/views/result/success/index.vue b/src/views/result/success/index.vue index ae57aba..0b7a29c 100644 --- a/src/views/result/success/index.vue +++ b/src/views/result/success/index.vue @@ -1,21 +1,21 @@ diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 973b1e7..f2098c9 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -1,479 +1,479 @@ diff --git a/src/views/system/menu/modules/menu-dialog.vue b/src/views/system/menu/modules/menu-dialog.vue index f512301..9c6ff93 100644 --- a/src/views/system/menu/modules/menu-dialog.vue +++ b/src/views/system/menu/modules/menu-dialog.vue @@ -1,384 +1,399 @@ diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index aca447e..7dbcbdb 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -1,242 +1,242 @@ diff --git a/src/views/system/role/modules/role-edit-dialog.vue b/src/views/system/role/modules/role-edit-dialog.vue index 46ff9b1..441e1b1 100644 --- a/src/views/system/role/modules/role-edit-dialog.vue +++ b/src/views/system/role/modules/role-edit-dialog.vue @@ -1,162 +1,162 @@ diff --git a/src/views/system/role/modules/role-permission-dialog.vue b/src/views/system/role/modules/role-permission-dialog.vue index 3691ac8..aaebfd1 100644 --- a/src/views/system/role/modules/role-permission-dialog.vue +++ b/src/views/system/role/modules/role-permission-dialog.vue @@ -1,254 +1,258 @@ diff --git a/src/views/system/role/modules/role-search.vue b/src/views/system/role/modules/role-search.vue index 1d59cee..fd7f875 100644 --- a/src/views/system/role/modules/role-search.vue +++ b/src/views/system/role/modules/role-search.vue @@ -1,121 +1,121 @@ diff --git a/src/views/system/user-center/index.vue b/src/views/system/user-center/index.vue index ab52f00..f2a34ea 100644 --- a/src/views/system/user-center/index.vue +++ b/src/views/system/user-center/index.vue @@ -1,247 +1,264 @@ diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index fb794d7..06eca0c 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -4,258 +4,266 @@ diff --git a/src/views/system/user/modules/user-dialog.vue b/src/views/system/user/modules/user-dialog.vue index 03cab4f..63beaf8 100644 --- a/src/views/system/user/modules/user-dialog.vue +++ b/src/views/system/user/modules/user-dialog.vue @@ -1,143 +1,143 @@ diff --git a/src/views/system/user/modules/user-search.vue b/src/views/system/user/modules/user-search.vue index e097720..dd395ec 100644 --- a/src/views/system/user/modules/user-search.vue +++ b/src/views/system/user/modules/user-search.vue @@ -1,112 +1,112 @@ diff --git a/tsconfig.json b/tsconfig.json index 4331962..1feedb4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,28 +1,28 @@ { - "compilerOptions": { - "target": "esnext", - "module": "esnext", - "moduleResolution": "node", - "strict": true, - "jsx": "preserve", - "sourceMap": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "lib": ["esnext", "dom"], - "types": ["vite/client", "node", "element-plus/global"], - "skipLibCheck": true, - "baseUrl": ".", - "paths": { - "@/*": ["src/*"], - "@views/*": ["src/views/*"], - "@imgs/*": ["src/assets/images/*"], - "@icons/*": ["src/assets/icons/*"], - "@utils/*": ["src/utils/*"], - "@stores/*": ["src/store/*"], - "@plugins/*": ["src/plugins/*"], - "@styles/*": ["src/assets/styles/*"] - } - }, - "include": ["src/**/*", "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], - "exclude": ["node_modules", "dist", "**/*.js"] + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "types": ["vite/client", "node", "element-plus/global"], + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"], + "@views/*": ["src/views/*"], + "@imgs/*": ["src/assets/images/*"], + "@icons/*": ["src/assets/icons/*"], + "@utils/*": ["src/utils/*"], + "@stores/*": ["src/store/*"], + "@plugins/*": ["src/plugins/*"], + "@styles/*": ["src/assets/styles/*"] + } + }, + "include": ["src/**/*", "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "exclude": ["node_modules", "dist", "**/*.js"] } diff --git a/vite.config.ts b/vite.config.ts index c2ef072..bb69fa0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,145 +12,145 @@ import tailwindcss from '@tailwindcss/vite' // import { visualizer } from 'rollup-plugin-visualizer' export default ({ mode }: { mode: string }) => { - const root = process.cwd() - const env = loadEnv(mode, root) - const { VITE_VERSION, VITE_PORT, VITE_BASE_URL, VITE_API_URL, VITE_API_PROXY_URL } = env + const root = process.cwd() + const env = loadEnv(mode, root) + const { VITE_VERSION, VITE_PORT, VITE_BASE_URL, VITE_API_URL, VITE_API_PROXY_URL } = env - console.log(`🚀 API_URL = ${VITE_API_URL}`) - console.log(`🚀 VERSION = ${VITE_VERSION}`) + console.log(`🚀 API_URL = ${VITE_API_URL}`) + console.log(`🚀 VERSION = ${VITE_VERSION}`) - return defineConfig({ - define: { - __APP_VERSION__: JSON.stringify(VITE_VERSION) - }, - base: VITE_BASE_URL, - server: { - port: Number(VITE_PORT), - proxy: { - '/api': { - target: VITE_API_PROXY_URL, - changeOrigin: true - } - }, - host: true - }, - // 路径别名 - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)), - '@views': resolvePath('src/views'), - '@imgs': resolvePath('src/assets/images'), - '@icons': resolvePath('src/assets/icons'), - '@utils': resolvePath('src/utils'), - '@stores': resolvePath('src/store'), - '@styles': resolvePath('src/assets/styles') - } - }, - build: { - target: 'es2015', - outDir: 'dist', - chunkSizeWarningLimit: 2000, - minify: 'terser', - terserOptions: { - compress: { - // 生产环境去除 console - drop_console: true, - // 生产环境去除 debugger - drop_debugger: true - } - }, - dynamicImportVarsOptions: { - warnOnError: true, - exclude: [], - include: ['src/views/**/*.vue'] - } - }, - plugins: [ - vue(), - tailwindcss(), - // 自动按需导入 API - AutoImport({ - imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'], - dts: 'src/types/import/auto-imports.d.ts', - resolvers: [ElementPlusResolver()], - eslintrc: { - enabled: true, - filepath: './.auto-import.json', - globalsPropValue: true - } - }), - // 自动按需导入组件 - Components({ - dts: 'src/types/import/components.d.ts', - resolvers: [ElementPlusResolver()] - }), - // 按需定制主题配置 - ElementPlus({ - useSource: true - }), - // 压缩 - viteCompression({ - verbose: false, // 是否在控制台输出压缩结果 - disable: false, // 是否禁用 - algorithm: 'gzip', // 压缩算法 - ext: '.gz', // 压缩后的文件名后缀 - threshold: 10240, // 只有大小大于该值的资源会被处理 10240B = 10KB - deleteOriginFile: false // 压缩后是否删除原文件 - }), - vueDevTools() - // 打包分析 - // visualizer({ - // open: true, - // gzipSize: true, - // brotliSize: true, - // filename: 'dist/stats.html' // 分析图生成的文件名及路径 - // }), - ], - // 依赖预构建:避免运行时重复请求与转换,提升首次加载速度 - optimizeDeps: { - include: [ - 'echarts/core', - 'echarts/charts', - 'echarts/components', - 'echarts/renderers', - 'xlsx', - 'xgplayer', - 'crypto-js', - 'file-saver', - 'vue-img-cutter', - 'element-plus/es', - 'element-plus/es/components/*/style/css', - 'element-plus/es/components/*/style/index' - ] - }, - css: { - preprocessorOptions: { - // sass variable and mixin - scss: { - additionalData: ` + return defineConfig({ + define: { + __APP_VERSION__: JSON.stringify(VITE_VERSION) + }, + base: VITE_BASE_URL, + server: { + port: Number(VITE_PORT), + proxy: { + '/api': { + target: VITE_API_PROXY_URL, + changeOrigin: true + } + }, + host: true + }, + // 路径别名 + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + '@views': resolvePath('src/views'), + '@imgs': resolvePath('src/assets/images'), + '@icons': resolvePath('src/assets/icons'), + '@utils': resolvePath('src/utils'), + '@stores': resolvePath('src/store'), + '@styles': resolvePath('src/assets/styles') + } + }, + build: { + target: 'es2015', + outDir: 'dist', + chunkSizeWarningLimit: 2000, + minify: 'terser', + terserOptions: { + compress: { + // 生产环境去除 console + drop_console: true, + // 生产环境去除 debugger + drop_debugger: true + } + }, + dynamicImportVarsOptions: { + warnOnError: true, + exclude: [], + include: ['src/views/**/*.vue'] + } + }, + plugins: [ + vue(), + tailwindcss(), + // 自动按需导入 API + AutoImport({ + imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'], + dts: 'src/types/import/auto-imports.d.ts', + resolvers: [ElementPlusResolver()], + eslintrc: { + enabled: true, + filepath: './.auto-import.json', + globalsPropValue: true + } + }), + // 自动按需导入组件 + Components({ + dts: 'src/types/import/components.d.ts', + resolvers: [ElementPlusResolver()] + }), + // 按需定制主题配置 + ElementPlus({ + useSource: true + }), + // 压缩 + viteCompression({ + verbose: false, // 是否在控制台输出压缩结果 + disable: false, // 是否禁用 + algorithm: 'gzip', // 压缩算法 + ext: '.gz', // 压缩后的文件名后缀 + threshold: 10240, // 只有大小大于该值的资源会被处理 10240B = 10KB + deleteOriginFile: false // 压缩后是否删除原文件 + }), + vueDevTools() + // 打包分析 + // visualizer({ + // open: true, + // gzipSize: true, + // brotliSize: true, + // filename: 'dist/stats.html' // 分析图生成的文件名及路径 + // }), + ], + // 依赖预构建:避免运行时重复请求与转换,提升首次加载速度 + optimizeDeps: { + include: [ + 'echarts/core', + 'echarts/charts', + 'echarts/components', + 'echarts/renderers', + 'xlsx', + 'xgplayer', + 'crypto-js', + 'file-saver', + 'vue-img-cutter', + 'element-plus/es', + 'element-plus/es/components/*/style/css', + 'element-plus/es/components/*/style/index' + ] + }, + css: { + preprocessorOptions: { + // sass variable and mixin + scss: { + additionalData: ` @use "@styles/core/el-light.scss" as *; @use "@styles/core/mixin.scss" as *; ` - } - }, - postcss: { - plugins: [ - { - postcssPlugin: 'internal:charset-removal', - AtRule: { - charset: (atRule) => { - if (atRule.name === 'charset') { - atRule.remove() - } - } - } - } - ] - } - } - }) + } + }, + postcss: { + plugins: [ + { + postcssPlugin: 'internal:charset-removal', + AtRule: { + charset: (atRule) => { + if (atRule.name === 'charset') { + atRule.remove() + } + } + } + } + ] + } + } + }) } function resolvePath(paths: string) { - return path.resolve(__dirname, paths) + return path.resolve(__dirname, paths) }