From 9e3b1f3c033de25559f1202fcc11d4b652751798 Mon Sep 17 00:00:00 2001 From: lvfengfree Date: Tue, 20 Jan 2026 15:00:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E6=A1=8C=E9=9D=A2Token=E5=88=86=E4=BA=AB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 WindowsCredential 模型和控制器,用于管理 Windows 凭据 - 新增 RemoteAccessToken 模型,支持生成可分享的远程访问链接 - 更新 RemoteDesktopController,添加 Token 生成、验证、撤销等 API - 更新前端 RemoteDesktopModal,支持4种连接方式:快速连接、生成分享链接、手动输入、链接管理 - 新增 WindowsCredentialManager 组件用于管理 Windows 凭据 - 新增 RemoteAccessPage 用于通过 Token 访问远程桌面 - 添加 Vue Router 支持 /remote/:token 路由 - 更新数据库迁移,添加 WindowsCredentials 和 RemoteAccessTokens 表 --- adminSystem/.env.development | 13 + adminSystem/.env.production | 10 + adminSystem/.gitattributes | 2 + adminSystem/.gitignore | 11 + adminSystem/.husky/commit-msg | 1 + adminSystem/.husky/pre-commit | 1 + adminSystem/.prettierignore | 3 + adminSystem/.prettierrc | 20 + adminSystem/.stylelintignore | 9 + adminSystem/.stylelintrc.cjs | 82 + adminSystem/LICENSE | 21 + adminSystem/commitlint.config.cjs | 97 + adminSystem/eslint.config.mjs | 83 + adminSystem/index.html | 47 + adminSystem/package.json | 123 + adminSystem/pnpm-lock.yaml | 10109 ++++++++++++++++ adminSystem/public/favicon.ico | Bin 0 -> 4286 bytes adminSystem/scripts/clean-dev.ts | 838 ++ adminSystem/src/App.vue | 34 + adminSystem/src/api/auth.ts | 29 + adminSystem/src/api/system-manage.ts | 25 + .../src/assets/images/avatar/avatar.webp | Bin 0 -> 954 bytes .../src/assets/images/avatar/avatar1.webp | Bin 0 -> 2296 bytes .../src/assets/images/avatar/avatar10.webp | Bin 0 -> 1410 bytes .../src/assets/images/avatar/avatar2.webp | Bin 0 -> 1214 bytes .../src/assets/images/avatar/avatar3.webp | Bin 0 -> 726 bytes .../src/assets/images/avatar/avatar4.webp | Bin 0 -> 944 bytes .../src/assets/images/avatar/avatar5.webp | Bin 0 -> 2272 bytes .../src/assets/images/avatar/avatar6.webp | Bin 0 -> 810 bytes .../src/assets/images/avatar/avatar7.webp | Bin 0 -> 2712 bytes .../src/assets/images/avatar/avatar8.webp | Bin 0 -> 3946 bytes .../src/assets/images/avatar/avatar9.webp | Bin 0 -> 1680 bytes adminSystem/src/assets/images/ceremony/hb.png | Bin 0 -> 2275 bytes adminSystem/src/assets/images/ceremony/sd.png | Bin 0 -> 4752 bytes adminSystem/src/assets/images/ceremony/xc.png | Bin 0 -> 4910 bytes adminSystem/src/assets/images/ceremony/yd.png | Bin 0 -> 4629 bytes .../src/assets/images/common/logo.webp | Bin 0 -> 2484 bytes adminSystem/src/assets/images/draw/draw1.png | Bin 0 -> 11315 bytes adminSystem/src/assets/images/favicon.ico | Bin 0 -> 4286 bytes .../src/assets/images/lock/bg_dark.webp | Bin 0 -> 70592 bytes .../src/assets/images/lock/bg_light.webp | Bin 0 -> 67246 bytes .../src/assets/images/login/lf_icon2.webp | Bin 0 -> 25016 bytes .../settings/menu_layouts/dual_column.png | Bin 0 -> 514 bytes .../settings/menu_layouts/horizontal.png | Bin 0 -> 409 bytes .../images/settings/menu_layouts/mixed.png | Bin 0 -> 431 bytes .../images/settings/menu_layouts/vertical.png | Bin 0 -> 439 bytes .../images/settings/menu_styles/dark.png | Bin 0 -> 292 bytes .../images/settings/menu_styles/design.png | Bin 0 -> 286 bytes .../images/settings/menu_styles/light.png | Bin 0 -> 293 bytes .../images/settings/theme_styles/dark.png | Bin 0 -> 448 bytes .../images/settings/theme_styles/light.png | Bin 0 -> 416 bytes .../images/settings/theme_styles/system.png | Bin 0 -> 509 bytes adminSystem/src/assets/images/svg/403.svg | 1 + adminSystem/src/assets/images/svg/404.svg | 1 + adminSystem/src/assets/images/svg/500.svg | 5 + .../src/assets/images/svg/login_icon.svg | 1 + .../src/assets/images/user/avatar.webp | Bin 0 -> 2130 bytes adminSystem/src/assets/images/user/bg.webp | Bin 0 -> 12352 bytes adminSystem/src/assets/styles/core/app.scss | 292 + adminSystem/src/assets/styles/core/dark.scss | 93 + .../src/assets/styles/core/el-dark.scss | 2 + .../src/assets/styles/core/el-light.scss | 34 + adminSystem/src/assets/styles/core/el-ui.scss | 524 + adminSystem/src/assets/styles/core/md.scss | 1036 ++ adminSystem/src/assets/styles/core/mixin.scss | 157 + adminSystem/src/assets/styles/core/reset.scss | 41 + .../assets/styles/core/router-transition.scss | 104 + .../src/assets/styles/core/tailwind.css | 208 + .../assets/styles/core/theme-animation.scss | 63 + .../src/assets/styles/core/theme-change.scss | 11 + .../assets/styles/custom/one-dark-pro.scss | 98 + adminSystem/src/assets/styles/index.scss | 23 + adminSystem/src/assets/svg/loading.ts | 32 + .../core/banners/art-basic-banner/index.vue | 343 + .../core/banners/art-card-banner/index.vue | 114 + .../core/base/art-back-to-top/index.vue | 40 + .../components/core/base/art-logo/index.vue | 21 + .../core/base/art-svg-icon/index.vue | 24 + .../core/cards/art-bar-chart-card/index.vue | 103 + .../core/cards/art-data-list-card/index.vue | 74 + .../core/cards/art-donut-chart-card/index.vue | 124 + .../core/cards/art-image-card/index.vue | 89 + .../core/cards/art-line-chart-card/index.vue | 126 + .../core/cards/art-progress-card/index.vue | 86 + .../core/cards/art-stats-card/index.vue | 67 + .../cards/art-timeline-list-card/index.vue | 69 + .../core/charts/art-bar-chart/index.vue | 203 + .../art-dual-bar-compare-chart/index.vue | 195 + .../core/charts/art-h-bar-chart/index.vue | 208 + .../core/charts/art-k-line-chart/index.vue | 152 + .../core/charts/art-line-chart/index.vue | 371 + .../core/charts/art-radar-chart/index.vue | 105 + .../core/charts/art-ring-chart/index.vue | 133 + .../core/charts/art-scatter-chart/index.vue | 115 + .../core/forms/art-button-more/index.vue | 71 + .../core/forms/art-button-table/index.vue | 59 + .../core/forms/art-drag-verify/index.vue | 430 + .../core/forms/art-excel-export/index.vue | 389 + .../core/forms/art-excel-import/index.vue | 62 + .../components/core/forms/art-form/index.vue | 311 + .../core/forms/art-search-bar/index.vue | 437 + .../core/forms/art-wang-editor/index.vue | 219 + .../core/forms/art-wang-editor/style.scss | 210 + .../core/layouts/art-breadcrumb/index.vue | 142 + .../core/layouts/art-chat-window/index.vue | 262 + .../core/layouts/art-fast-enter/index.vue | 113 + .../layouts/art-fireworks-effect/index.vue | 633 + .../layouts/art-global-component/index.vue | 14 + .../core/layouts/art-global-search/index.vue | 426 + .../core/layouts/art-header-bar/index.vue | 485 + .../art-header-bar/widget/ArtUserMenu.vue | 167 + .../art-menus/art-horizontal-menu/index.vue | 110 + .../widget/HorizontalSubmenu.vue | 95 + .../art-menus/art-mixed-menu/index.vue | 279 + .../art-menus/art-sidebar-menu/index.vue | 355 + .../art-menus/art-sidebar-menu/style.scss | 253 + .../art-menus/art-sidebar-menu/theme.scss | 258 + .../widget/SidebarSubmenu.vue | 188 + .../core/layouts/art-notification/index.vue | 456 + .../core/layouts/art-page-content/index.vue | 136 + .../core/layouts/art-screen-lock/index.vue | 517 + .../composables/useSettingsConfig.ts | 248 + .../composables/useSettingsHandlers.ts | 167 + .../composables/useSettingsPanel.ts | 192 + .../composables/useSettingsState.ts | 37 + .../core/layouts/art-settings-panel/index.vue | 72 + .../layouts/art-settings-panel/style.scss | 92 + .../widget/BasicSettings.vue | 77 + .../widget/BoxStyleSettings.vue | 38 + .../widget/ColorSettings.vue | 35 + .../widget/ContainerSettings.vue | 33 + .../widget/MenuLayoutSettings.vue | 31 + .../widget/MenuStyleSettings.vue | 44 + .../widget/SectionTitle.vue | 17 + .../widget/SettingActions.vue | 235 + .../widget/SettingDrawer.vue | 51 + .../widget/SettingHeader.vue | 18 + .../art-settings-panel/widget/SettingItem.vue | 101 + .../widget/ThemeSettings.vue | 28 + .../core/layouts/art-work-tab/index.vue | 584 + .../core/media/art-cutter-img/index.vue | 350 + .../core/media/art-video-player/index.vue | 111 + .../core/others/art-menu-right/index.vue | 415 + .../core/others/art-watermark/index.vue | 64 + .../core/tables/art-table-header/index.vue | 339 + .../core/tables/art-table/index.vue | 342 + .../core/tables/art-table/style.scss | 99 + .../core/text-effect/art-count-to/index.vue | 310 + .../art-festival-text-scroll/index.vue | 32 + .../text-effect/art-text-scroll/index.vue | 285 + .../components/core/theme/theme-svg/index.vue | 100 + .../core/views/exception/ArtException.vue | 43 + .../core/views/login/AuthTopBar.vue | 149 + .../core/views/login/LoginLeftView.vue | 602 + .../core/views/result/ArtResultPage.vue | 43 + .../core/widget/art-icon-button/index.vue | 23 + adminSystem/src/config/assets/images.ts | 61 + adminSystem/src/config/fastEnter.ts | 79 + adminSystem/src/config/index.ts | 135 + adminSystem/src/config/modules/component.ts | 105 + adminSystem/src/config/modules/fastEnter.ts | 127 + adminSystem/src/config/modules/festival.ts | 51 + adminSystem/src/config/modules/headerBar.ts | 63 + adminSystem/src/config/setting.ts | 109 + .../src/directives/business/highlight.ts | 248 + adminSystem/src/directives/business/ripple.ts | 114 + adminSystem/src/directives/core/auth.ts | 68 + adminSystem/src/directives/core/roles.ts | 89 + adminSystem/src/directives/index.ts | 12 + adminSystem/src/enums/appEnum.ts | 81 + adminSystem/src/enums/formEnum.ts | 24 + adminSystem/src/env.d.ts | 34 + adminSystem/src/hooks/core/useAppMode.ts | 45 + adminSystem/src/hooks/core/useAuth.ts | 74 + adminSystem/src/hooks/core/useCeremony.ts | 184 + adminSystem/src/hooks/core/useChart.ts | 745 ++ adminSystem/src/hooks/core/useCommon.ts | 87 + adminSystem/src/hooks/core/useFastEnter.ts | 55 + adminSystem/src/hooks/core/useHeaderBar.ts | 201 + adminSystem/src/hooks/core/useLayoutHeight.ts | 148 + adminSystem/src/hooks/core/useTable.ts | 736 ++ adminSystem/src/hooks/core/useTableColumns.ts | 256 + adminSystem/src/hooks/core/useTableHeight.ts | 105 + adminSystem/src/hooks/core/useTheme.ts | 174 + adminSystem/src/hooks/index.ts | 32 + adminSystem/src/locales/index.ts | 123 + adminSystem/src/locales/langs/en.json | 296 + adminSystem/src/locales/langs/zh.json | 296 + adminSystem/src/main.ts | 25 + adminSystem/src/mock/temp/formData.ts | 273 + adminSystem/src/mock/upgrade/changeLog.ts | 12 + adminSystem/src/plugins/echarts.ts | 76 + adminSystem/src/plugins/index.ts | 6 + .../src/router/core/ComponentLoader.ts | 82 + .../src/router/core/IframeRouteManager.ts | 78 + adminSystem/src/router/core/MenuProcessor.ts | 241 + .../router/core/RoutePermissionValidator.ts | 119 + adminSystem/src/router/core/RouteRegistry.ts | 90 + .../src/router/core/RouteTransformer.ts | 132 + adminSystem/src/router/core/RouteValidator.ts | 187 + adminSystem/src/router/core/index.ts | 14 + adminSystem/src/router/guards/afterEach.ts | 34 + adminSystem/src/router/guards/beforeEach.ts | 360 + adminSystem/src/router/index.ts | 23 + adminSystem/src/router/modules/dashboard.ts | 24 + adminSystem/src/router/modules/exception.ts | 46 + adminSystem/src/router/modules/index.ts | 15 + adminSystem/src/router/modules/result.ts | 33 + adminSystem/src/router/modules/system.ts | 60 + adminSystem/src/router/routes/asyncRoutes.ts | 9 + adminSystem/src/router/routes/staticRoutes.ts | 72 + adminSystem/src/router/routesAlias.ts | 8 + adminSystem/src/store/index.ts | 52 + adminSystem/src/store/modules/menu.ts | 109 + adminSystem/src/store/modules/setting.ts | 450 + adminSystem/src/store/modules/table.ts | 97 + adminSystem/src/store/modules/user.ts | 235 + adminSystem/src/store/modules/worktab.ts | 568 + adminSystem/src/types/api/api.d.ts | 135 + adminSystem/src/types/common/index.ts | 95 + adminSystem/src/types/common/response.ts | 30 + adminSystem/src/types/component/chart.ts | 324 + adminSystem/src/types/component/index.ts | 145 + adminSystem/src/types/config/index.ts | 211 + adminSystem/src/types/index.ts | 22 + adminSystem/src/types/router/index.ts | 80 + adminSystem/src/types/store/index.ts | 157 + adminSystem/src/utils/constants/index.ts | 8 + adminSystem/src/utils/constants/links.ts | 35 + adminSystem/src/utils/form/index.ts | 12 + adminSystem/src/utils/form/responsive.ts | 122 + adminSystem/src/utils/form/validator.ts | 316 + adminSystem/src/utils/http/error.ts | 182 + adminSystem/src/utils/http/index.ts | 214 + adminSystem/src/utils/http/status.ts | 18 + adminSystem/src/utils/index.ts | 34 + adminSystem/src/utils/navigation/index.ts | 10 + adminSystem/src/utils/navigation/jump.ts | 62 + adminSystem/src/utils/navigation/route.ts | 78 + adminSystem/src/utils/navigation/worktab.ts | 67 + adminSystem/src/utils/router.ts | 61 + adminSystem/src/utils/socket/index.ts | 388 + adminSystem/src/utils/storage/index.ts | 7 + .../src/utils/storage/storage-config.ts | 122 + .../src/utils/storage/storage-key-manager.ts | 97 + adminSystem/src/utils/storage/storage.ts | 250 + adminSystem/src/utils/sys/console.ts | 13 + adminSystem/src/utils/sys/error-handle.ts | 102 + adminSystem/src/utils/sys/index.ts | 6 + adminSystem/src/utils/sys/mittBus.ts | 63 + adminSystem/src/utils/sys/upgrade.ts | 277 + adminSystem/src/utils/table/tableCache.ts | 266 + adminSystem/src/utils/table/tableConfig.ts | 55 + adminSystem/src/utils/table/tableUtils.ts | 297 + adminSystem/src/utils/ui/animation.ts | 80 + adminSystem/src/utils/ui/colors.ts | 273 + adminSystem/src/utils/ui/emojo.ts | 24 + adminSystem/src/utils/ui/iconify-loader.ts | 31 + adminSystem/src/utils/ui/index.ts | 11 + adminSystem/src/utils/ui/loading.ts | 84 + adminSystem/src/utils/ui/tabs.ts | 60 + .../src/views/auth/forget-password/index.vue | 62 + adminSystem/src/views/auth/login/index.vue | 285 + adminSystem/src/views/auth/login/style.css | 38 + adminSystem/src/views/auth/register/index.vue | 240 + .../src/views/dashboard/console/index.vue | 41 + .../console/modules/about-project.vue | 44 + .../dashboard/console/modules/active-user.vue | 47 + .../dashboard/console/modules/card-list.vue | 74 + .../console/modules/dynamic-stats.vue | 79 + .../dashboard/console/modules/new-user.vue | 169 + .../console/modules/sales-overview.vue | 43 + .../dashboard/console/modules/todo-list.vue | 71 + adminSystem/src/views/exception/403/index.vue | 16 + adminSystem/src/views/exception/404/index.vue | 16 + adminSystem/src/views/exception/500/index.vue | 16 + adminSystem/src/views/index/index.vue | 29 + adminSystem/src/views/index/style.scss | 93 + adminSystem/src/views/outside/Iframe.vue | 42 + adminSystem/src/views/result/fail/index.vue | 28 + .../src/views/result/success/index.vue | 21 + adminSystem/src/views/system/menu/index.vue | 479 + .../views/system/menu/modules/menu-dialog.vue | 384 + adminSystem/src/views/system/role/index.vue | 242 + .../system/role/modules/role-edit-dialog.vue | 162 + .../role/modules/role-permission-dialog.vue | 254 + .../views/system/role/modules/role-search.vue | 121 + .../src/views/system/user-center/index.vue | 247 + adminSystem/src/views/system/user/index.vue | 261 + .../views/system/user/modules/user-dialog.vue | 143 + .../views/system/user/modules/user-search.vue | 112 + adminSystem/tsconfig.json | 28 + adminSystem/vite.config.ts | 156 + .../Controllers/RemoteDesktopController.cs | 183 +- .../WindowsCredentialsController.cs | 195 + .../AmtScanner.Api/Data/AppDbContext.cs | 31 + ...edentialsAndRemoteAccessTokens.Designer.cs | 384 + ...WindowsCredentialsAndRemoteAccessTokens.cs | 110 + .../Migrations/AppDbContextModelSnapshot.cs | 114 + .../Models/RemoteAccessToken.cs | 85 + .../Models/WindowsCredential.cs | 60 + backend-csharp/AmtScanner.Api/Program.cs | 22 +- .../AmtScanner.Api/fix-migration.sql | 7 + frontend/package-lock.json | 18 +- frontend/package.json | 5 +- frontend/src/App.vue | 103 +- frontend/src/api/scanApi.js | 71 + frontend/src/components/MainLayout.vue | 100 + frontend/src/components/RemoteAccessPage.vue | 206 + .../src/components/RemoteDesktopModal.vue | 426 +- .../components/WindowsCredentialManager.vue | 246 + frontend/src/main.js | 2 + frontend/src/router/index.js | 23 + 313 files changed, 48996 insertions(+), 379 deletions(-) create mode 100644 adminSystem/.env.development create mode 100644 adminSystem/.env.production create mode 100644 adminSystem/.gitattributes create mode 100644 adminSystem/.gitignore create mode 100644 adminSystem/.husky/commit-msg create mode 100644 adminSystem/.husky/pre-commit create mode 100644 adminSystem/.prettierignore create mode 100644 adminSystem/.prettierrc create mode 100644 adminSystem/.stylelintignore create mode 100644 adminSystem/.stylelintrc.cjs create mode 100644 adminSystem/LICENSE create mode 100644 adminSystem/commitlint.config.cjs create mode 100644 adminSystem/eslint.config.mjs create mode 100644 adminSystem/index.html create mode 100644 adminSystem/package.json create mode 100644 adminSystem/pnpm-lock.yaml create mode 100644 adminSystem/public/favicon.ico create mode 100644 adminSystem/scripts/clean-dev.ts create mode 100644 adminSystem/src/App.vue create mode 100644 adminSystem/src/api/auth.ts create mode 100644 adminSystem/src/api/system-manage.ts create mode 100644 adminSystem/src/assets/images/avatar/avatar.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar1.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar10.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar2.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar3.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar4.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar5.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar6.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar7.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar8.webp create mode 100644 adminSystem/src/assets/images/avatar/avatar9.webp create mode 100644 adminSystem/src/assets/images/ceremony/hb.png create mode 100644 adminSystem/src/assets/images/ceremony/sd.png create mode 100644 adminSystem/src/assets/images/ceremony/xc.png create mode 100644 adminSystem/src/assets/images/ceremony/yd.png create mode 100644 adminSystem/src/assets/images/common/logo.webp create mode 100644 adminSystem/src/assets/images/draw/draw1.png create mode 100644 adminSystem/src/assets/images/favicon.ico create mode 100644 adminSystem/src/assets/images/lock/bg_dark.webp create mode 100644 adminSystem/src/assets/images/lock/bg_light.webp create mode 100644 adminSystem/src/assets/images/login/lf_icon2.webp create mode 100644 adminSystem/src/assets/images/settings/menu_layouts/dual_column.png create mode 100644 adminSystem/src/assets/images/settings/menu_layouts/horizontal.png create mode 100644 adminSystem/src/assets/images/settings/menu_layouts/mixed.png create mode 100644 adminSystem/src/assets/images/settings/menu_layouts/vertical.png create mode 100644 adminSystem/src/assets/images/settings/menu_styles/dark.png create mode 100644 adminSystem/src/assets/images/settings/menu_styles/design.png create mode 100644 adminSystem/src/assets/images/settings/menu_styles/light.png create mode 100644 adminSystem/src/assets/images/settings/theme_styles/dark.png create mode 100644 adminSystem/src/assets/images/settings/theme_styles/light.png create mode 100644 adminSystem/src/assets/images/settings/theme_styles/system.png create mode 100644 adminSystem/src/assets/images/svg/403.svg create mode 100644 adminSystem/src/assets/images/svg/404.svg create mode 100644 adminSystem/src/assets/images/svg/500.svg create mode 100644 adminSystem/src/assets/images/svg/login_icon.svg create mode 100644 adminSystem/src/assets/images/user/avatar.webp create mode 100644 adminSystem/src/assets/images/user/bg.webp create mode 100644 adminSystem/src/assets/styles/core/app.scss create mode 100644 adminSystem/src/assets/styles/core/dark.scss create mode 100644 adminSystem/src/assets/styles/core/el-dark.scss create mode 100644 adminSystem/src/assets/styles/core/el-light.scss create mode 100644 adminSystem/src/assets/styles/core/el-ui.scss create mode 100644 adminSystem/src/assets/styles/core/md.scss create mode 100644 adminSystem/src/assets/styles/core/mixin.scss create mode 100644 adminSystem/src/assets/styles/core/reset.scss create mode 100644 adminSystem/src/assets/styles/core/router-transition.scss create mode 100644 adminSystem/src/assets/styles/core/tailwind.css create mode 100644 adminSystem/src/assets/styles/core/theme-animation.scss create mode 100644 adminSystem/src/assets/styles/core/theme-change.scss create mode 100644 adminSystem/src/assets/styles/custom/one-dark-pro.scss create mode 100644 adminSystem/src/assets/styles/index.scss create mode 100644 adminSystem/src/assets/svg/loading.ts create mode 100644 adminSystem/src/components/core/banners/art-basic-banner/index.vue create mode 100644 adminSystem/src/components/core/banners/art-card-banner/index.vue create mode 100644 adminSystem/src/components/core/base/art-back-to-top/index.vue create mode 100644 adminSystem/src/components/core/base/art-logo/index.vue create mode 100644 adminSystem/src/components/core/base/art-svg-icon/index.vue create mode 100644 adminSystem/src/components/core/cards/art-bar-chart-card/index.vue create mode 100644 adminSystem/src/components/core/cards/art-data-list-card/index.vue create mode 100644 adminSystem/src/components/core/cards/art-donut-chart-card/index.vue create mode 100644 adminSystem/src/components/core/cards/art-image-card/index.vue create mode 100644 adminSystem/src/components/core/cards/art-line-chart-card/index.vue create mode 100644 adminSystem/src/components/core/cards/art-progress-card/index.vue create mode 100644 adminSystem/src/components/core/cards/art-stats-card/index.vue create mode 100644 adminSystem/src/components/core/cards/art-timeline-list-card/index.vue create mode 100644 adminSystem/src/components/core/charts/art-bar-chart/index.vue create mode 100644 adminSystem/src/components/core/charts/art-dual-bar-compare-chart/index.vue create mode 100644 adminSystem/src/components/core/charts/art-h-bar-chart/index.vue create mode 100644 adminSystem/src/components/core/charts/art-k-line-chart/index.vue create mode 100644 adminSystem/src/components/core/charts/art-line-chart/index.vue create mode 100644 adminSystem/src/components/core/charts/art-radar-chart/index.vue create mode 100644 adminSystem/src/components/core/charts/art-ring-chart/index.vue create mode 100644 adminSystem/src/components/core/charts/art-scatter-chart/index.vue create mode 100644 adminSystem/src/components/core/forms/art-button-more/index.vue create mode 100644 adminSystem/src/components/core/forms/art-button-table/index.vue create mode 100644 adminSystem/src/components/core/forms/art-drag-verify/index.vue create mode 100644 adminSystem/src/components/core/forms/art-excel-export/index.vue create mode 100644 adminSystem/src/components/core/forms/art-excel-import/index.vue create mode 100644 adminSystem/src/components/core/forms/art-form/index.vue create mode 100644 adminSystem/src/components/core/forms/art-search-bar/index.vue create mode 100644 adminSystem/src/components/core/forms/art-wang-editor/index.vue create mode 100644 adminSystem/src/components/core/forms/art-wang-editor/style.scss create mode 100644 adminSystem/src/components/core/layouts/art-breadcrumb/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-chat-window/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-fast-enter/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-fireworks-effect/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-global-component/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-global-search/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-header-bar/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue create mode 100644 adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue create mode 100644 adminSystem/src/components/core/layouts/art-menus/art-mixed-menu/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss create mode 100644 adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss create mode 100644 adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue create mode 100644 adminSystem/src/components/core/layouts/art-notification/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-page-content/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-screen-lock/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/index.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/style.scss create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue create mode 100644 adminSystem/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue create mode 100644 adminSystem/src/components/core/layouts/art-work-tab/index.vue create mode 100644 adminSystem/src/components/core/media/art-cutter-img/index.vue create mode 100644 adminSystem/src/components/core/media/art-video-player/index.vue create mode 100644 adminSystem/src/components/core/others/art-menu-right/index.vue create mode 100644 adminSystem/src/components/core/others/art-watermark/index.vue create mode 100644 adminSystem/src/components/core/tables/art-table-header/index.vue create mode 100644 adminSystem/src/components/core/tables/art-table/index.vue create mode 100644 adminSystem/src/components/core/tables/art-table/style.scss create mode 100644 adminSystem/src/components/core/text-effect/art-count-to/index.vue create mode 100644 adminSystem/src/components/core/text-effect/art-festival-text-scroll/index.vue create mode 100644 adminSystem/src/components/core/text-effect/art-text-scroll/index.vue create mode 100644 adminSystem/src/components/core/theme/theme-svg/index.vue create mode 100644 adminSystem/src/components/core/views/exception/ArtException.vue create mode 100644 adminSystem/src/components/core/views/login/AuthTopBar.vue create mode 100644 adminSystem/src/components/core/views/login/LoginLeftView.vue create mode 100644 adminSystem/src/components/core/views/result/ArtResultPage.vue create mode 100644 adminSystem/src/components/core/widget/art-icon-button/index.vue create mode 100644 adminSystem/src/config/assets/images.ts create mode 100644 adminSystem/src/config/fastEnter.ts create mode 100644 adminSystem/src/config/index.ts create mode 100644 adminSystem/src/config/modules/component.ts create mode 100644 adminSystem/src/config/modules/fastEnter.ts create mode 100644 adminSystem/src/config/modules/festival.ts create mode 100644 adminSystem/src/config/modules/headerBar.ts create mode 100644 adminSystem/src/config/setting.ts create mode 100644 adminSystem/src/directives/business/highlight.ts create mode 100644 adminSystem/src/directives/business/ripple.ts create mode 100644 adminSystem/src/directives/core/auth.ts create mode 100644 adminSystem/src/directives/core/roles.ts create mode 100644 adminSystem/src/directives/index.ts create mode 100644 adminSystem/src/enums/appEnum.ts create mode 100644 adminSystem/src/enums/formEnum.ts create mode 100644 adminSystem/src/env.d.ts create mode 100644 adminSystem/src/hooks/core/useAppMode.ts create mode 100644 adminSystem/src/hooks/core/useAuth.ts create mode 100644 adminSystem/src/hooks/core/useCeremony.ts create mode 100644 adminSystem/src/hooks/core/useChart.ts create mode 100644 adminSystem/src/hooks/core/useCommon.ts create mode 100644 adminSystem/src/hooks/core/useFastEnter.ts create mode 100644 adminSystem/src/hooks/core/useHeaderBar.ts create mode 100644 adminSystem/src/hooks/core/useLayoutHeight.ts create mode 100644 adminSystem/src/hooks/core/useTable.ts create mode 100644 adminSystem/src/hooks/core/useTableColumns.ts create mode 100644 adminSystem/src/hooks/core/useTableHeight.ts create mode 100644 adminSystem/src/hooks/core/useTheme.ts create mode 100644 adminSystem/src/hooks/index.ts create mode 100644 adminSystem/src/locales/index.ts create mode 100644 adminSystem/src/locales/langs/en.json create mode 100644 adminSystem/src/locales/langs/zh.json create mode 100644 adminSystem/src/main.ts create mode 100644 adminSystem/src/mock/temp/formData.ts create mode 100644 adminSystem/src/mock/upgrade/changeLog.ts create mode 100644 adminSystem/src/plugins/echarts.ts create mode 100644 adminSystem/src/plugins/index.ts create mode 100644 adminSystem/src/router/core/ComponentLoader.ts create mode 100644 adminSystem/src/router/core/IframeRouteManager.ts create mode 100644 adminSystem/src/router/core/MenuProcessor.ts create mode 100644 adminSystem/src/router/core/RoutePermissionValidator.ts create mode 100644 adminSystem/src/router/core/RouteRegistry.ts create mode 100644 adminSystem/src/router/core/RouteTransformer.ts create mode 100644 adminSystem/src/router/core/RouteValidator.ts create mode 100644 adminSystem/src/router/core/index.ts create mode 100644 adminSystem/src/router/guards/afterEach.ts create mode 100644 adminSystem/src/router/guards/beforeEach.ts create mode 100644 adminSystem/src/router/index.ts create mode 100644 adminSystem/src/router/modules/dashboard.ts create mode 100644 adminSystem/src/router/modules/exception.ts create mode 100644 adminSystem/src/router/modules/index.ts create mode 100644 adminSystem/src/router/modules/result.ts create mode 100644 adminSystem/src/router/modules/system.ts create mode 100644 adminSystem/src/router/routes/asyncRoutes.ts create mode 100644 adminSystem/src/router/routes/staticRoutes.ts create mode 100644 adminSystem/src/router/routesAlias.ts create mode 100644 adminSystem/src/store/index.ts create mode 100644 adminSystem/src/store/modules/menu.ts create mode 100644 adminSystem/src/store/modules/setting.ts create mode 100644 adminSystem/src/store/modules/table.ts create mode 100644 adminSystem/src/store/modules/user.ts create mode 100644 adminSystem/src/store/modules/worktab.ts create mode 100644 adminSystem/src/types/api/api.d.ts create mode 100644 adminSystem/src/types/common/index.ts create mode 100644 adminSystem/src/types/common/response.ts create mode 100644 adminSystem/src/types/component/chart.ts create mode 100644 adminSystem/src/types/component/index.ts create mode 100644 adminSystem/src/types/config/index.ts create mode 100644 adminSystem/src/types/index.ts create mode 100644 adminSystem/src/types/router/index.ts create mode 100644 adminSystem/src/types/store/index.ts create mode 100644 adminSystem/src/utils/constants/index.ts create mode 100644 adminSystem/src/utils/constants/links.ts create mode 100644 adminSystem/src/utils/form/index.ts create mode 100644 adminSystem/src/utils/form/responsive.ts create mode 100644 adminSystem/src/utils/form/validator.ts create mode 100644 adminSystem/src/utils/http/error.ts create mode 100644 adminSystem/src/utils/http/index.ts create mode 100644 adminSystem/src/utils/http/status.ts create mode 100644 adminSystem/src/utils/index.ts create mode 100644 adminSystem/src/utils/navigation/index.ts create mode 100644 adminSystem/src/utils/navigation/jump.ts create mode 100644 adminSystem/src/utils/navigation/route.ts create mode 100644 adminSystem/src/utils/navigation/worktab.ts create mode 100644 adminSystem/src/utils/router.ts create mode 100644 adminSystem/src/utils/socket/index.ts create mode 100644 adminSystem/src/utils/storage/index.ts create mode 100644 adminSystem/src/utils/storage/storage-config.ts create mode 100644 adminSystem/src/utils/storage/storage-key-manager.ts create mode 100644 adminSystem/src/utils/storage/storage.ts create mode 100644 adminSystem/src/utils/sys/console.ts create mode 100644 adminSystem/src/utils/sys/error-handle.ts create mode 100644 adminSystem/src/utils/sys/index.ts create mode 100644 adminSystem/src/utils/sys/mittBus.ts create mode 100644 adminSystem/src/utils/sys/upgrade.ts create mode 100644 adminSystem/src/utils/table/tableCache.ts create mode 100644 adminSystem/src/utils/table/tableConfig.ts create mode 100644 adminSystem/src/utils/table/tableUtils.ts create mode 100644 adminSystem/src/utils/ui/animation.ts create mode 100644 adminSystem/src/utils/ui/colors.ts create mode 100644 adminSystem/src/utils/ui/emojo.ts create mode 100644 adminSystem/src/utils/ui/iconify-loader.ts create mode 100644 adminSystem/src/utils/ui/index.ts create mode 100644 adminSystem/src/utils/ui/loading.ts create mode 100644 adminSystem/src/utils/ui/tabs.ts create mode 100644 adminSystem/src/views/auth/forget-password/index.vue create mode 100644 adminSystem/src/views/auth/login/index.vue create mode 100644 adminSystem/src/views/auth/login/style.css create mode 100644 adminSystem/src/views/auth/register/index.vue create mode 100644 adminSystem/src/views/dashboard/console/index.vue create mode 100644 adminSystem/src/views/dashboard/console/modules/about-project.vue create mode 100644 adminSystem/src/views/dashboard/console/modules/active-user.vue create mode 100644 adminSystem/src/views/dashboard/console/modules/card-list.vue create mode 100644 adminSystem/src/views/dashboard/console/modules/dynamic-stats.vue create mode 100644 adminSystem/src/views/dashboard/console/modules/new-user.vue create mode 100644 adminSystem/src/views/dashboard/console/modules/sales-overview.vue create mode 100644 adminSystem/src/views/dashboard/console/modules/todo-list.vue create mode 100644 adminSystem/src/views/exception/403/index.vue create mode 100644 adminSystem/src/views/exception/404/index.vue create mode 100644 adminSystem/src/views/exception/500/index.vue create mode 100644 adminSystem/src/views/index/index.vue create mode 100644 adminSystem/src/views/index/style.scss create mode 100644 adminSystem/src/views/outside/Iframe.vue create mode 100644 adminSystem/src/views/result/fail/index.vue create mode 100644 adminSystem/src/views/result/success/index.vue create mode 100644 adminSystem/src/views/system/menu/index.vue create mode 100644 adminSystem/src/views/system/menu/modules/menu-dialog.vue create mode 100644 adminSystem/src/views/system/role/index.vue create mode 100644 adminSystem/src/views/system/role/modules/role-edit-dialog.vue create mode 100644 adminSystem/src/views/system/role/modules/role-permission-dialog.vue create mode 100644 adminSystem/src/views/system/role/modules/role-search.vue create mode 100644 adminSystem/src/views/system/user-center/index.vue create mode 100644 adminSystem/src/views/system/user/index.vue create mode 100644 adminSystem/src/views/system/user/modules/user-dialog.vue create mode 100644 adminSystem/src/views/system/user/modules/user-search.vue create mode 100644 adminSystem/tsconfig.json create mode 100644 adminSystem/vite.config.ts create mode 100644 backend-csharp/AmtScanner.Api/Controllers/WindowsCredentialsController.cs create mode 100644 backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.Designer.cs create mode 100644 backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.cs create mode 100644 backend-csharp/AmtScanner.Api/Models/RemoteAccessToken.cs create mode 100644 backend-csharp/AmtScanner.Api/Models/WindowsCredential.cs create mode 100644 backend-csharp/AmtScanner.Api/fix-migration.sql create mode 100644 frontend/src/components/MainLayout.vue create mode 100644 frontend/src/components/RemoteAccessPage.vue create mode 100644 frontend/src/components/WindowsCredentialManager.vue create mode 100644 frontend/src/router/index.js diff --git a/adminSystem/.env.development b/adminSystem/.env.development new file mode 100644 index 0000000..10cc209 --- /dev/null +++ b/adminSystem/.env.development @@ -0,0 +1,13 @@ +# 【开发】环境变量 + +# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/) +VITE_BASE_URL = / + +# API 请求基础路径(开发环境设置为 / 使用代理,生产环境设置为完整后端地址) +VITE_API_URL = / + +# 代理目标地址(开发环境通过 Vite 代理转发请求到此地址,解决跨域问题) +VITE_API_PROXY_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default + +# Delete console +VITE_DROP_CONSOLE = false \ No newline at end of file diff --git a/adminSystem/.env.production b/adminSystem/.env.production new file mode 100644 index 0000000..89e0aaa --- /dev/null +++ b/adminSystem/.env.production @@ -0,0 +1,10 @@ +# 【生产】环境变量 + +# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/) +VITE_BASE_URL = / + +# API 地址前缀 +VITE_API_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default + +# Delete console +VITE_DROP_CONSOLE = true \ No newline at end of file diff --git a/adminSystem/.gitattributes b/adminSystem/.gitattributes new file mode 100644 index 0000000..866e8ee --- /dev/null +++ b/adminSystem/.gitattributes @@ -0,0 +1,2 @@ +*.html linguist-detectable=false +*.vue linguist-detectable=true diff --git a/adminSystem/.gitignore b/adminSystem/.gitignore new file mode 100644 index 0000000..e48d2e9 --- /dev/null +++ b/adminSystem/.gitignore @@ -0,0 +1,11 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +.cursorrules + +# Auto-generated files +src/types/import/auto-imports.d.ts +src/types/import/components.d.ts +.auto-import.json diff --git a/adminSystem/.husky/commit-msg b/adminSystem/.husky/commit-msg new file mode 100644 index 0000000..09d2b14 --- /dev/null +++ b/adminSystem/.husky/commit-msg @@ -0,0 +1 @@ +pnpm dlx commitlint --edit $1 \ No newline at end of file diff --git a/adminSystem/.husky/pre-commit b/adminSystem/.husky/pre-commit new file mode 100644 index 0000000..22c0347 --- /dev/null +++ b/adminSystem/.husky/pre-commit @@ -0,0 +1 @@ +pnpm run lint:lint-staged \ No newline at end of file diff --git a/adminSystem/.prettierignore b/adminSystem/.prettierignore new file mode 100644 index 0000000..9e96efc --- /dev/null +++ b/adminSystem/.prettierignore @@ -0,0 +1,3 @@ +/node_modules/* +/dist/* +/src/main.ts \ No newline at end of file diff --git a/adminSystem/.prettierrc b/adminSystem/.prettierrc new file mode 100644 index 0000000..f3d6ad5 --- /dev/null +++ b/adminSystem/.prettierrc @@ -0,0 +1,20 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "vueIndentScriptAndStyle": true, + "singleQuote": true, + "quoteProps": "as-needed", + "bracketSpacing": true, + "trailingComma": "none", + "bracketSameLine": false, + "jsxSingleQuote": false, + "arrowParens": "always", + "insertPragma": false, + "requirePragma": false, + "proseWrap": "never", + "htmlWhitespaceSensitivity": "strict", + "endOfLine": "auto", + "rangeStart": 0 +} diff --git a/adminSystem/.stylelintignore b/adminSystem/.stylelintignore new file mode 100644 index 0000000..476ea45 --- /dev/null +++ b/adminSystem/.stylelintignore @@ -0,0 +1,9 @@ +dist +node_modules +public +.husky +.vscode + +src/components/Layout/MenuLeft/index.vue +src/assets +stats.html \ No newline at end of file diff --git a/adminSystem/.stylelintrc.cjs b/adminSystem/.stylelintrc.cjs new file mode 100644 index 0000000..9dbea0b --- /dev/null +++ b/adminSystem/.stylelintrc.cjs @@ -0,0 +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' + ] + } + ] + } +} diff --git a/adminSystem/LICENSE b/adminSystem/LICENSE new file mode 100644 index 0000000..68322de --- /dev/null +++ b/adminSystem/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 SuperManTT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/adminSystem/commitlint.config.cjs b/adminSystem/commitlint.config.cjs new file mode 100644 index 0000000..d2ef1bd --- /dev/null +++ b/adminSystem/commitlint.config.cjs @@ -0,0 +1,97 @@ +/** + * commitlint 配置文件 + * 文档 + * https://commitlint.js.org/#/reference-rules + * https://cz-git.qbb.sh/zh/guide/ + */ + +module.exports = { + // 继承的规则 + extends: ['@commitlint/config-conventional'], + // 自定义规则 + rules: { + // 提交类型枚举,git提交type必须是以下类型 + 'type-enum': [ + 2, + 'always', + [ + 'feat', // 新增功能 + 'fix', // 修复缺陷 + 'docs', // 文档变更 + 'style', // 代码格式(不影响功能,例如空格、分号等格式修正) + 'refactor', // 代码重构(不包括 bug 修复、功能新增) + 'perf', // 性能优化 + 'test', // 添加疏漏测试或已有测试改动 + 'build', // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等) + 'ci', // 修改 CI 配置、脚本 + 'revert', // 回滚 commit + 'chore', // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例) + 'wip' // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例) + ] + ], + 'subject-case': [0] // subject大小写不做校验 + }, + + prompt: { + messages: { + type: '选择你要提交的类型 :', + scope: '选择一个提交范围(可选):', + customScope: '请输入自定义的提交范围 :', + subject: '填写简短精炼的变更描述 :\n', + body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n', + breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n', + footerPrefixesSelect: '选择关联issue前缀(可选):', + customFooterPrefix: '输入自定义issue前缀 :', + footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', + generatingByAI: '正在通过 AI 生成你的提交简短描述...', + generatedSelectByAI: '选择一个 AI 生成的简短描述:', + confirmCommit: '是否提交或修改commit ?' + }, + // prettier-ignore + types: [ + { value: "feat", name: "feat: 新增功能" }, + { value: "fix", name: "fix: 修复缺陷" }, + { value: "docs", name: "docs: 文档变更" }, + { value: "style", name: "style: 代码格式(不影响功能,例如空格、分号等格式修正)" }, + { value: "refactor", name: "refactor: 代码重构(不包括 bug 修复、功能新增)" }, + { value: "perf", name: "perf: 性能优化" }, + { value: "test", name: "test: 添加疏漏测试或已有测试改动" }, + { value: "build", name: "build: 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)" }, + { value: "ci", name: "ci: 修改 CI 配置、脚本" }, + { value: "revert", name: "revert: 回滚 commit" }, + { value: "chore", name: "chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)" }, + ], + useEmoji: true, + emojiAlign: 'center', + useAI: false, + aiNumber: 1, + themeColorCode: '', + scopes: [], + allowCustomScopes: true, + allowEmptyScopes: true, + customScopesAlign: 'bottom', + customScopesAlias: 'custom', + emptyScopesAlias: 'empty', + upperCaseSubject: false, + markBreakingChangeMode: false, + allowBreakingChanges: ['feat', 'fix'], + breaklineNumber: 100, + breaklineChar: '|', + skipQuestions: ['breaking', 'footerPrefix', 'footer'], // 跳过的步骤 + issuePrefixes: [{ value: 'closed', name: 'closed: ISSUES has been processed' }], + customIssuePrefixAlign: 'top', + emptyIssuePrefixAlias: 'skip', + customIssuePrefixAlias: 'custom', + allowCustomIssuePrefix: true, + allowEmptyIssuePrefix: true, + confirmColorize: true, + maxHeaderLength: Infinity, + maxSubjectLength: Infinity, + minSubjectLength: 0, + scopeOverrides: undefined, + defaultBody: '', + defaultIssues: '', + defaultScope: '', + defaultSubject: '' + } +} diff --git a/adminSystem/eslint.config.mjs b/adminSystem/eslint.config.mjs new file mode 100644 index 0000000..bb317f6 --- /dev/null +++ b/adminSystem/eslint.config.mjs @@ -0,0 +1,83 @@ +// 从 URL 和路径模块中导入必要的功能 +import fs from 'fs' +import path, { dirname } from 'path' +import { fileURLToPath } from 'url' + +// 从 ESLint 插件中导入推荐配置 +import pluginJs from '@eslint/js' +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' +import pluginVue from 'eslint-plugin-vue' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +// 使用 import.meta.url 获取当前模块的路径 +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +// 读取 .auto-import.json 文件的内容,并将其解析为 JSON 对象 +const autoImportConfig = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '.auto-import.json'), 'utf-8') +) + +export default [ + // 指定文件匹配规则 + { + files: ['**/*.{js,mjs,cjs,ts,vue}'] + }, + // 指定全局变量和环境 + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node + } + } + }, + // 扩展配置 + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + ...pluginVue.configs['flat/essential'], + // 自定义规则 + { + // 针对所有 JavaScript、TypeScript 和 Vue 文件应用以下配置 + files: ['**/*.{js,mjs,cjs,ts,vue}'], + + languageOptions: { + globals: { + // 合并从 autoImportConfig 中读取的全局变量配置 + ...autoImportConfig.globals, + // TypeScript 全局命名空间 + Api: 'readonly' + } + }, + rules: { + quotes: ['error', 'single'], // 使用单引号 + semi: ['error', 'never'], // 语句末尾不加分号 + 'no-var': 'error', // 要求使用 let 或 const 而不是 var + '@typescript-eslint/no-explicit-any': 'off', // 禁用 any 检查 + 'vue/multi-word-component-names': 'off', // 禁用对 Vue 组件名称的多词要求检查 + 'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行 + 'no-unexpected-multiline': 'error' // 禁止空余的多行 + } + }, + // vue 规则 + { + files: ['**/*.vue'], + languageOptions: { + parserOptions: { parser: tseslint.parser } + } + }, + // 忽略文件 + { + ignores: [ + 'node_modules', + 'dist', + 'public', + '.vscode/**', + 'src/assets/**', + 'src/utils/console.ts' + ] + }, + // prettier 配置 + eslintPluginPrettierRecommended +] diff --git a/adminSystem/index.html b/adminSystem/index.html new file mode 100644 index 0000000..ba51ce5 --- /dev/null +++ b/adminSystem/index.html @@ -0,0 +1,47 @@ + + + + Art Design Pro + + + + + + + + + + + +
+ + + diff --git a/adminSystem/package.json b/adminSystem/package.json new file mode 100644 index 0000000..7b66362 --- /dev/null +++ b/adminSystem/package.json @@ -0,0 +1,123 @@ +{ + "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": { + "@commitlint/cli": "^19.4.1", + "@commitlint/config-conventional": "^19.4.1", + "@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", + "commitizen": "^4.3.0", + "cz-git": "^1.11.1", + "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", + "husky": "^9.1.5", + "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/adminSystem/pnpm-lock.yaml b/adminSystem/pnpm-lock.yaml new file mode 100644 index 0000000..401c518 --- /dev/null +++ b/adminSystem/pnpm-lock.yaml @@ -0,0 +1,10109 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.2 + version: 2.3.2(vue@3.5.22(typescript@5.6.3)) + '@iconify/vue': + specifier: ^5.0.0 + version: 5.0.0(vue@3.5.22(typescript@5.6.3)) + '@tailwindcss/vite': + specifier: ^4.1.14 + version: 4.1.14(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + '@vue/reactivity': + specifier: ^3.5.21 + version: 3.5.22 + '@vueuse/core': + specifier: ^13.9.0 + version: 13.9.0(vue@3.5.22(typescript@5.6.3)) + '@wangeditor/editor': + specifier: ^5.1.23 + version: 5.1.23 + '@wangeditor/editor-for-vue': + specifier: next + version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.22(typescript@5.6.3)) + axios: + specifier: ^1.12.2 + version: 1.12.2 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + echarts: + specifier: ^6.0.0 + version: 6.0.0 + element-plus: + specifier: ^2.11.2 + version: 2.11.4(vue@3.5.22(typescript@5.6.3)) + file-saver: + specifier: ^2.0.5 + version: 2.0.5 + highlight.js: + specifier: ^11.10.0 + version: 11.11.1 + mitt: + specifier: ^3.0.1 + version: 3.0.1 + nprogress: + specifier: ^0.2.0 + version: 0.2.0 + ohash: + specifier: ^2.0.11 + version: 2.0.11 + pinia: + specifier: ^3.0.3 + version: 3.0.3(typescript@5.6.3)(vue@3.5.22(typescript@5.6.3)) + pinia-plugin-persistedstate: + specifier: ^4.3.0 + version: 4.5.0(pinia@3.0.3(typescript@5.6.3)(vue@3.5.22(typescript@5.6.3))) + qrcode.vue: + specifier: ^3.6.0 + version: 3.6.0(vue@3.5.22(typescript@5.6.3)) + tailwindcss: + specifier: ^4.1.14 + version: 4.1.14 + vue: + specifier: ^3.5.21 + version: 3.5.22(typescript@5.6.3) + vue-draggable-plus: + specifier: ^0.6.0 + version: 0.6.0(@types/sortablejs@1.15.8) + vue-i18n: + specifier: ^9.14.0 + version: 9.14.5(vue@3.5.22(typescript@5.6.3)) + vue-router: + specifier: ^4.5.1 + version: 4.5.1(vue@3.5.22(typescript@5.6.3)) + xgplayer: + specifier: ^3.0.20 + version: 3.0.23(core-js@3.45.1) + xlsx: + specifier: ^0.18.5 + version: 0.18.5 + devDependencies: + '@commitlint/cli': + specifier: ^19.4.1 + version: 19.8.1(@types/node@24.8.1)(typescript@5.6.3) + '@commitlint/config-conventional': + specifier: ^19.4.1 + version: 19.8.1 + '@eslint/js': + specifier: ^9.9.1 + version: 9.36.0 + '@types/node': + specifier: ^24.0.5 + version: 24.8.1 + '@typescript-eslint/eslint-plugin': + specifier: ^8.3.0 + version: 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + '@typescript-eslint/parser': + specifier: ^8.3.0 + version: 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.1(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.6.3)) + '@vue/compiler-sfc': + specifier: ^3.0.5 + version: 3.5.22 + commitizen: + specifier: ^4.3.0 + version: 4.3.1(@types/node@24.8.1)(typescript@5.6.3) + cz-git: + specifier: ^1.11.1 + version: 1.12.0 + eslint: + specifier: ^9.9.1 + version: 9.36.0(jiti@2.6.0) + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.2(eslint@9.36.0(jiti@2.6.0)) + eslint-plugin-prettier: + specifier: ^5.2.1 + version: 5.5.4(eslint-config-prettier@9.1.2(eslint@9.36.0(jiti@2.6.0)))(eslint@9.36.0(jiti@2.6.0))(prettier@3.6.2) + eslint-plugin-vue: + specifier: ^9.27.0 + version: 9.33.0(eslint@9.36.0(jiti@2.6.0)) + globals: + specifier: ^15.9.0 + version: 15.15.0 + husky: + specifier: ^9.1.5 + version: 9.1.7 + lint-staged: + specifier: ^15.5.2 + version: 15.5.2 + prettier: + specifier: ^3.5.3 + version: 3.6.2 + rollup-plugin-visualizer: + specifier: ^5.12.0 + version: 5.14.0(rollup@4.52.3) + sass: + specifier: ^1.81.0 + version: 1.93.2 + stylelint: + specifier: ^16.20.0 + version: 16.24.0(typescript@5.6.3) + stylelint-config-html: + specifier: ^1.1.0 + version: 1.1.0(postcss-html@1.8.0)(stylelint@16.24.0(typescript@5.6.3)) + stylelint-config-recess-order: + specifier: ^4.6.0 + version: 4.6.0(stylelint@16.24.0(typescript@5.6.3)) + stylelint-config-recommended-scss: + specifier: ^14.1.0 + version: 14.1.0(postcss@8.5.6)(stylelint@16.24.0(typescript@5.6.3)) + stylelint-config-recommended-vue: + specifier: ^1.5.0 + version: 1.6.1(postcss-html@1.8.0)(stylelint@16.24.0(typescript@5.6.3)) + stylelint-config-standard: + specifier: ^36.0.1 + version: 36.0.1(stylelint@16.24.0(typescript@5.6.3)) + terser: + specifier: ^5.36.0 + version: 5.44.0 + tsx: + specifier: ^4.20.3 + version: 4.20.6 + typescript: + specifier: ~5.6.3 + version: 5.6.3 + typescript-eslint: + specifier: ^8.9.0 + version: 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + unplugin-auto-import: + specifier: ^20.2.0 + version: 20.2.0(@vueuse/core@13.9.0(vue@3.5.22(typescript@5.6.3))) + unplugin-element-plus: + specifier: ^0.10.0 + version: 0.10.0 + unplugin-vue-components: + specifier: ^29.1.0 + version: 29.1.0(@babel/parser@7.28.4)(vue@3.5.22(typescript@5.6.3)) + vite: + specifier: ^7.1.5 + version: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vite-plugin-compression: + specifier: ^0.5.1 + version: 0.5.1(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + vite-plugin-vue-devtools: + specifier: ^7.7.6 + version: 7.7.7(rollup@4.52.3)(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.6.3)) + vue-demi: + specifier: ^0.14.9 + version: 0.14.10(vue@3.5.22(typescript@5.6.3)) + vue-img-cutter: + specifier: ^3.0.5 + version: 3.0.7(typescript@5.6.3) + vue-tsc: + specifier: ~2.1.6 + version: 2.1.10(typescript@5.6.3) + +packages: + '@antfu/utils@0.7.10': + resolution: + { + integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww== + } + + '@babel/code-frame@7.27.1': + resolution: + { + integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + } + engines: { node: '>=6.9.0' } + + '@babel/compat-data@7.28.4': + resolution: + { + integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw== + } + engines: { node: '>=6.9.0' } + + '@babel/core@7.28.4': + resolution: + { + integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== + } + engines: { node: '>=6.9.0' } + + '@babel/generator@7.28.3': + resolution: + { + integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: + { + integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-compilation-targets@7.27.2': + resolution: + { + integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-create-class-features-plugin@7.28.3': + resolution: + { + integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: + { + integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: + { + integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-imports@7.27.1': + resolution: + { + integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-transforms@7.28.3': + resolution: + { + integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: + { + integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-plugin-utils@7.27.1': + resolution: + { + integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-replace-supers@7.27.1': + resolution: + { + integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: + { + integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-string-parser@7.27.1': + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-identifier@7.27.1': + resolution: + { + integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-option@7.27.1': + resolution: + { + integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + } + engines: { node: '>=6.9.0' } + + '@babel/helpers@7.28.4': + resolution: + { + integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + } + engines: { node: '>=6.9.0' } + + '@babel/parser@7.28.4': + resolution: + { + integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + } + engines: { node: '>=6.0.0' } + hasBin: true + + '@babel/plugin-proposal-decorators@7.28.0': + resolution: + { + integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.27.1': + resolution: + { + integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: + { + integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: + { + integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: + { + integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: + { + integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.0': + resolution: + { + integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg== + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.4': + resolution: + { + integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + } + engines: { node: '>=6.9.0' } + + '@babel/template@7.27.2': + resolution: + { + integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + } + engines: { node: '>=6.9.0' } + + '@babel/traverse@7.28.4': + resolution: + { + integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + } + engines: { node: '>=6.9.0' } + + '@babel/types@7.28.4': + resolution: + { + integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + } + engines: { node: '>=6.9.0' } + + '@cacheable/memoize@2.0.2': + resolution: + { + integrity: sha512-wPrr7FUiq3Qt4yQyda2/NcOLTJCFcQSU3Am2adP+WLy+sz93/fKTokVTHmtz+rjp4PD7ee0AEOeRVNN6IvIfsg== + } + + '@cacheable/memory@2.0.2': + resolution: + { + integrity: sha512-sJTITLfeCI1rg7P3ssaGmQryq235EGT8dXGcx6oZwX5NRnKq9IE6lddlllcOl+oXW+yaeTRddCjo0xrfU6ZySA== + } + + '@cacheable/utils@2.0.2': + resolution: + { + integrity: sha512-JTFM3raFhVv8LH95T7YnZbf2YoE9wEtkPPStuRF9a6ExZ103hFvs+QyCuYJ6r0hA9wRtbzgZtwUCoDWxssZd4Q== + } + + '@commitlint/cli@19.8.1': + resolution: + { + integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA== + } + engines: { node: '>=v18' } + hasBin: true + + '@commitlint/config-conventional@19.8.1': + resolution: + { + integrity: sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ== + } + engines: { node: '>=v18' } + + '@commitlint/config-validator@19.8.1': + resolution: + { + integrity: sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ== + } + engines: { node: '>=v18' } + + '@commitlint/config-validator@20.0.0': + resolution: + { + integrity: sha512-BeyLMaRIJDdroJuYM2EGhDMGwVBMZna9UiIqV9hxj+J551Ctc6yoGuGSmghOy/qPhBSuhA6oMtbEiTmxECafsg== + } + engines: { node: '>=v18' } + + '@commitlint/ensure@19.8.1': + resolution: + { + integrity: sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw== + } + engines: { node: '>=v18' } + + '@commitlint/execute-rule@19.8.1': + resolution: + { + integrity: sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA== + } + engines: { node: '>=v18' } + + '@commitlint/execute-rule@20.0.0': + resolution: + { + integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw== + } + engines: { node: '>=v18' } + + '@commitlint/format@19.8.1': + resolution: + { + integrity: sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw== + } + engines: { node: '>=v18' } + + '@commitlint/is-ignored@19.8.1': + resolution: + { + integrity: sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg== + } + engines: { node: '>=v18' } + + '@commitlint/lint@19.8.1': + resolution: + { + integrity: sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw== + } + engines: { node: '>=v18' } + + '@commitlint/load@19.8.1': + resolution: + { + integrity: sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A== + } + engines: { node: '>=v18' } + + '@commitlint/load@20.0.0': + resolution: + { + integrity: sha512-WiNKO9fDPlLY90Rruw2HqHKcghrmj5+kMDJ4GcTlX1weL8K07Q6b27C179DxnsrjGCRAKVwFKyzxV4x+xDY28Q== + } + engines: { node: '>=v18' } + + '@commitlint/message@19.8.1': + resolution: + { + integrity: sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg== + } + engines: { node: '>=v18' } + + '@commitlint/parse@19.8.1': + resolution: + { + integrity: sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw== + } + engines: { node: '>=v18' } + + '@commitlint/read@19.8.1': + resolution: + { + integrity: sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ== + } + engines: { node: '>=v18' } + + '@commitlint/resolve-extends@19.8.1': + resolution: + { + integrity: sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg== + } + engines: { node: '>=v18' } + + '@commitlint/resolve-extends@20.0.0': + resolution: + { + integrity: sha512-BA4vva1hY8y0/Hl80YDhe9TJZpRFMsUYzVxvwTLPTEBotbGx/gS49JlVvtF1tOCKODQp7pS7CbxCpiceBgp3Dg== + } + engines: { node: '>=v18' } + + '@commitlint/rules@19.8.1': + resolution: + { + integrity: sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw== + } + engines: { node: '>=v18' } + + '@commitlint/to-lines@19.8.1': + resolution: + { + integrity: sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg== + } + engines: { node: '>=v18' } + + '@commitlint/top-level@19.8.1': + resolution: + { + integrity: sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw== + } + engines: { node: '>=v18' } + + '@commitlint/types@19.8.1': + resolution: + { + integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw== + } + engines: { node: '>=v18' } + + '@commitlint/types@20.0.0': + resolution: + { + integrity: sha512-bVUNBqG6aznYcYjTjnc3+Cat/iBgbgpflxbIBTnsHTX0YVpnmINPEkSRWymT2Q8aSH3Y7aKnEbunilkYe8TybA== + } + engines: { node: '>=v18' } + + '@csstools/css-parser-algorithms@3.0.5': + resolution: + { + integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== + } + engines: { node: '>=18' } + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: + { + integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== + } + engines: { node: '>=18' } + + '@csstools/media-query-list-parser@4.0.3': + resolution: + { + integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ== + } + engines: { node: '>=18' } + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/selector-specificity@5.0.0': + resolution: + { + integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw== + } + engines: { node: '>=18' } + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@ctrl/tinycolor@3.6.1': + resolution: + { + integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA== + } + engines: { node: '>=10' } + + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: + { + integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg== + } + + '@element-plus/icons-vue@2.3.2': + resolution: + { + integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A== + } + peerDependencies: + vue: ^3.2.0 + + '@esbuild/aix-ppc64@0.25.10': + resolution: + { + integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw== + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.10': + resolution: + { + integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.10': + resolution: + { + integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w== + } + engines: { node: '>=18' } + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.10': + resolution: + { + integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg== + } + engines: { node: '>=18' } + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.10': + resolution: + { + integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.10': + resolution: + { + integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg== + } + engines: { node: '>=18' } + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.10': + resolution: + { + integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.10': + resolution: + { + integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA== + } + engines: { node: '>=18' } + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.10': + resolution: + { + integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.10': + resolution: + { + integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg== + } + engines: { node: '>=18' } + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.10': + resolution: + { + integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ== + } + engines: { node: '>=18' } + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.10': + resolution: + { + integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg== + } + engines: { node: '>=18' } + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.10': + resolution: + { + integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA== + } + engines: { node: '>=18' } + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.10': + resolution: + { + integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA== + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.10': + resolution: + { + integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA== + } + engines: { node: '>=18' } + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.10': + resolution: + { + integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew== + } + engines: { node: '>=18' } + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.10': + resolution: + { + integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA== + } + engines: { node: '>=18' } + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: + { + integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.10': + resolution: + { + integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig== + } + engines: { node: '>=18' } + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.10': + resolution: + { + integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.10': + resolution: + { + integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw== + } + engines: { node: '>=18' } + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: + { + integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.10': + resolution: + { + integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ== + } + engines: { node: '>=18' } + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.10': + resolution: + { + integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw== + } + engines: { node: '>=18' } + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.10': + resolution: + { + integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw== + } + engines: { node: '>=18' } + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.10': + resolution: + { + integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw== + } + engines: { node: '>=18' } + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: + { + integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: + { + integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + '@eslint/config-array@0.21.0': + resolution: + { + integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/config-helpers@0.3.1': + resolution: + { + integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/core@0.15.2': + resolution: + { + integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/eslintrc@3.3.1': + resolution: + { + integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/js@9.36.0': + resolution: + { + integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/object-schema@2.1.6': + resolution: + { + integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/plugin-kit@0.3.5': + resolution: + { + integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@floating-ui/core@1.7.3': + resolution: + { + integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w== + } + + '@floating-ui/dom@1.7.4': + resolution: + { + integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA== + } + + '@floating-ui/utils@0.2.10': + resolution: + { + integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== + } + + '@humanfs/core@0.19.1': + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + } + engines: { node: '>=18.18.0' } + + '@humanfs/node@0.16.7': + resolution: + { + integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + } + engines: { node: '>=18.18.0' } + + '@humanwhocodes/module-importer@1.0.1': + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + } + engines: { node: '>=12.22' } + + '@humanwhocodes/retry@0.4.3': + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + } + engines: { node: '>=18.18' } + + '@iconify/types@2.0.0': + resolution: + { + integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== + } + + '@iconify/vue@5.0.0': + resolution: + { + integrity: sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg== + } + peerDependencies: + vue: '>=3' + + '@intlify/core-base@9.14.5': + resolution: + { + integrity: sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA== + } + engines: { node: '>= 16' } + + '@intlify/message-compiler@9.14.5': + resolution: + { + integrity: sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ== + } + engines: { node: '>= 16' } + + '@intlify/shared@9.14.5': + resolution: + { + integrity: sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ== + } + engines: { node: '>= 16' } + + '@isaacs/fs-minipass@4.0.1': + resolution: + { + integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + } + engines: { node: '>=18.0.0' } + + '@jridgewell/gen-mapping@0.3.13': + resolution: + { + integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + } + + '@jridgewell/remapping@2.3.5': + resolution: + { + integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + } + + '@jridgewell/resolve-uri@3.1.2': + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + } + engines: { node: '>=6.0.0' } + + '@jridgewell/source-map@0.3.11': + resolution: + { + integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== + } + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: + { + integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + } + + '@jridgewell/trace-mapping@0.3.31': + resolution: + { + integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + } + + '@keyv/bigmap@1.0.2': + resolution: + { + integrity: sha512-KR03xkEZlAZNF4IxXgVXb+uNIVNvwdh8UwI0cnc7WI6a+aQcDp8GL80qVfeB4E5NpsKJzou5jU0r6yLSSbMOtA== + } + engines: { node: '>= 18' } + + '@keyv/serialize@1.1.1': + resolution: + { + integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA== + } + + '@nodelib/fs.scandir@2.1.5': + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + } + engines: { node: '>= 8' } + + '@nodelib/fs.stat@2.0.5': + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + } + engines: { node: '>= 8' } + + '@nodelib/fs.walk@1.2.8': + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + } + engines: { node: '>= 8' } + + '@parcel/watcher-android-arm64@2.5.1': + resolution: + { + integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== + } + engines: { node: '>= 10.0.0' } + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: + { + integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== + } + engines: { node: '>= 10.0.0' } + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: + { + integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== + } + engines: { node: '>= 10.0.0' } + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: + { + integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== + } + engines: { node: '>= 10.0.0' } + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: + { + integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== + } + engines: { node: '>= 10.0.0' } + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: + { + integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== + } + engines: { node: '>= 10.0.0' } + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: + { + integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== + } + engines: { node: '>= 10.0.0' } + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: + { + integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== + } + engines: { node: '>= 10.0.0' } + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: + { + integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== + } + engines: { node: '>= 10.0.0' } + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: + { + integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== + } + engines: { node: '>= 10.0.0' } + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: + { + integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== + } + engines: { node: '>= 10.0.0' } + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: + { + integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== + } + engines: { node: '>= 10.0.0' } + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: + { + integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== + } + engines: { node: '>= 10.0.0' } + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: + { + integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== + } + engines: { node: '>= 10.0.0' } + + '@pkgr/core@0.2.9': + resolution: + { + integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + + '@polka/url@1.0.0-next.29': + resolution: + { + integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww== + } + + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: + { + integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q== + } + + '@rollup/pluginutils@5.3.0': + resolution: + { + integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q== + } + engines: { node: '>=14.0.0' } + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.52.3': + resolution: + { + integrity: sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw== + } + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.3': + resolution: + { + integrity: sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw== + } + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.3': + resolution: + { + integrity: sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg== + } + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.3': + resolution: + { + integrity: sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A== + } + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.3': + resolution: + { + integrity: sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ== + } + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.3': + resolution: + { + integrity: sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A== + } + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': + resolution: + { + integrity: sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA== + } + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.52.3': + resolution: + { + integrity: sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA== + } + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.52.3': + resolution: + { + integrity: sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ== + } + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.52.3': + resolution: + { + integrity: sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw== + } + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.52.3': + resolution: + { + integrity: sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg== + } + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.52.3': + resolution: + { + integrity: sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw== + } + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.52.3': + resolution: + { + integrity: sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg== + } + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.52.3': + resolution: + { + integrity: sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg== + } + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.52.3': + resolution: + { + integrity: sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg== + } + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.52.3': + resolution: + { + integrity: sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA== + } + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.52.3': + resolution: + { + integrity: sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw== + } + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.52.3': + resolution: + { + integrity: sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA== + } + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.3': + resolution: + { + integrity: sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA== + } + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.3': + resolution: + { + integrity: sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g== + } + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.3': + resolution: + { + integrity: sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ== + } + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.3': + resolution: + { + integrity: sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA== + } + cpu: [x64] + os: [win32] + + '@sec-ant/readable-stream@0.4.1': + resolution: + { + integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== + } + + '@sindresorhus/merge-streams@4.0.0': + resolution: + { + integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ== + } + engines: { node: '>=18' } + + '@sxzz/popperjs-es@2.11.7': + resolution: + { + integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ== + } + + '@tailwindcss/node@4.1.14': + resolution: + { + integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw== + } + + '@tailwindcss/oxide-android-arm64@4.1.14': + resolution: + { + integrity: sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ== + } + engines: { node: '>= 10' } + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.14': + resolution: + { + integrity: sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA== + } + engines: { node: '>= 10' } + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.14': + resolution: + { + integrity: sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw== + } + engines: { node: '>= 10' } + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.14': + resolution: + { + integrity: sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw== + } + engines: { node: '>= 10' } + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + resolution: + { + integrity: sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw== + } + engines: { node: '>= 10' } + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + resolution: + { + integrity: sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w== + } + engines: { node: '>= 10' } + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + resolution: + { + integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ== + } + engines: { node: '>= 10' } + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + resolution: + { + integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg== + } + engines: { node: '>= 10' } + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.1.14': + resolution: + { + integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q== + } + engines: { node: '>= 10' } + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.1.14': + resolution: + { + integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ== + } + engines: { node: '>=14.0.0' } + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + resolution: + { + integrity: sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA== + } + engines: { node: '>= 10' } + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + resolution: + { + integrity: sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA== + } + engines: { node: '>= 10' } + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.14': + resolution: + { + integrity: sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw== + } + engines: { node: '>= 10' } + + '@tailwindcss/vite@4.1.14': + resolution: + { + integrity: sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA== + } + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@transloadit/prettier-bytes@0.0.7': + resolution: + { + integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA== + } + + '@types/conventional-commits-parser@5.0.1': + resolution: + { + integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ== + } + + '@types/estree@1.0.8': + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + } + + '@types/event-emitter@0.3.5': + resolution: + { + integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ== + } + + '@types/json-schema@7.0.15': + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + } + + '@types/lodash-es@4.17.12': + resolution: + { + integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ== + } + + '@types/lodash@4.17.20': + resolution: + { + integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== + } + + '@types/node@24.8.1': + resolution: + { + integrity: sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q== + } + + '@types/sortablejs@1.15.8': + resolution: + { + integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg== + } + + '@types/web-bluetooth@0.0.16': + resolution: + { + integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ== + } + + '@types/web-bluetooth@0.0.21': + resolution: + { + integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA== + } + + '@typescript-eslint/eslint-plugin@8.44.1': + resolution: + { + integrity: sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + '@typescript-eslint/parser': ^8.44.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.44.1': + resolution: + { + integrity: sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.44.1': + resolution: + { + integrity: sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.44.1': + resolution: + { + integrity: sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/tsconfig-utils@8.44.1': + resolution: + { + integrity: sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.44.1': + resolution: + { + integrity: sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.44.1': + resolution: + { + integrity: sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/typescript-estree@8.44.1': + resolution: + { + integrity: sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.44.1': + resolution: + { + integrity: sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.44.1': + resolution: + { + integrity: sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@uppy/companion-client@2.2.2': + resolution: + { + integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og== + } + + '@uppy/core@2.3.4': + resolution: + { + integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ== + } + + '@uppy/store-default@2.1.1': + resolution: + { + integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ== + } + + '@uppy/utils@4.1.3': + resolution: + { + integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw== + } + + '@uppy/xhr-upload@2.1.3': + resolution: + { + integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ== + } + peerDependencies: + '@uppy/core': ^2.3.3 + + '@vitejs/plugin-vue@6.0.1': + resolution: + { + integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw== + } + engines: { node: ^20.19.0 || >=22.12.0 } + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.23': + resolution: + { + integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ== + } + + '@volar/source-map@2.4.23': + resolution: + { + integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q== + } + + '@volar/typescript@2.4.23': + resolution: + { + integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag== + } + + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: + { + integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA== + } + + '@vue/babel-plugin-jsx@1.5.0': + resolution: + { + integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw== + } + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: + { + integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w== + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.22': + resolution: + { + integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ== + } + + '@vue/compiler-dom@3.5.22': + resolution: + { + integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA== + } + + '@vue/compiler-sfc@3.5.22': + resolution: + { + integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ== + } + + '@vue/compiler-ssr@3.5.22': + resolution: + { + integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww== + } + + '@vue/compiler-vue2@2.7.16': + resolution: + { + integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A== + } + + '@vue/devtools-api@6.6.4': + resolution: + { + integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g== + } + + '@vue/devtools-api@7.7.7': + resolution: + { + integrity: sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg== + } + + '@vue/devtools-core@7.7.7': + resolution: + { + integrity: sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ== + } + peerDependencies: + vue: ^3.0.0 + + '@vue/devtools-kit@7.7.7': + resolution: + { + integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA== + } + + '@vue/devtools-shared@7.7.7': + resolution: + { + integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw== + } + + '@vue/language-core@2.1.10': + resolution: + { + integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ== + } + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.22': + resolution: + { + integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A== + } + + '@vue/runtime-core@3.5.22': + resolution: + { + integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ== + } + + '@vue/runtime-dom@3.5.22': + resolution: + { + integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww== + } + + '@vue/server-renderer@3.5.22': + resolution: + { + integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ== + } + peerDependencies: + vue: 3.5.22 + + '@vue/shared@3.5.22': + resolution: + { + integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w== + } + + '@vueuse/core@13.9.0': + resolution: + { + integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA== + } + peerDependencies: + vue: ^3.5.0 + + '@vueuse/core@9.13.0': + resolution: + { + integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw== + } + + '@vueuse/metadata@13.9.0': + resolution: + { + integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg== + } + + '@vueuse/metadata@9.13.0': + resolution: + { + integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ== + } + + '@vueuse/shared@13.9.0': + resolution: + { + integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g== + } + peerDependencies: + vue: ^3.5.0 + + '@vueuse/shared@9.13.0': + resolution: + { + integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw== + } + + '@wangeditor/basic-modules@1.1.7': + resolution: + { + integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg== + } + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/code-highlight@1.0.3': + resolution: + { + integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw== + } + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/core@1.1.19': + resolution: + { + integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q== + } + peerDependencies: + '@uppy/core': ^2.1.1 + '@uppy/xhr-upload': ^2.0.3 + dom7: ^3.0.0 + is-hotkey: ^0.2.0 + lodash.camelcase: ^4.3.0 + lodash.clonedeep: ^4.5.0 + lodash.debounce: ^4.0.8 + lodash.foreach: ^4.5.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + lodash.toarray: ^4.4.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/editor-for-vue@5.1.12': + resolution: + { + integrity: sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ== + } + peerDependencies: + '@wangeditor/editor': '>=5.1.0' + vue: ^3.0.5 + + '@wangeditor/editor@5.1.23': + resolution: + { + integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ== + } + + '@wangeditor/list-module@1.0.5': + resolution: + { + integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ== + } + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/table-module@1.1.4': + resolution: + { + integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w== + } + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/upload-image-module@1.0.2': + resolution: + { + integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA== + } + peerDependencies: + '@uppy/core': ^2.0.3 + '@uppy/xhr-upload': ^2.0.3 + '@wangeditor/basic-modules': 1.x + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.foreach: ^4.5.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/video-module@1.1.4': + resolution: + { + integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg== + } + peerDependencies: + '@uppy/core': ^2.1.4 + '@uppy/xhr-upload': ^2.0.7 + '@wangeditor/core': 1.x + dom7: ^3.0.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + JSONStream@1.3.5: + resolution: + { + integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + } + hasBin: true + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + } + engines: { node: '>=0.4.0' } + hasBin: true + + adler-32@1.3.1: + resolution: + { + integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A== + } + engines: { node: '>=0.8' } + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + } + + ajv@8.17.1: + resolution: + { + integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + } + + alien-signals@0.2.2: + resolution: + { + integrity: sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A== + } + + ansi-escapes@4.3.2: + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + } + engines: { node: '>=8' } + + ansi-escapes@7.1.1: + resolution: + { + integrity: sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q== + } + engines: { node: '>=18' } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + } + engines: { node: '>=8' } + + ansi-regex@6.2.2: + resolution: + { + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + } + engines: { node: '>=12' } + + ansi-styles@3.2.1: + resolution: + { + integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + } + engines: { node: '>=4' } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + } + engines: { node: '>=8' } + + ansi-styles@6.2.3: + resolution: + { + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + } + engines: { node: '>=12' } + + anymatch@3.1.3: + resolution: + { + integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + } + engines: { node: '>= 8' } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + } + + array-ify@1.0.0: + resolution: + { + integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== + } + + array-union@2.1.0: + resolution: + { + integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + } + engines: { node: '>=8' } + + astral-regex@2.0.0: + resolution: + { + integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + } + engines: { node: '>=8' } + + async-validator@4.2.5: + resolution: + { + integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== + } + + asynckit@0.4.0: + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + } + + at-least-node@1.0.0: + resolution: + { + integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + } + engines: { node: '>= 4.0.0' } + + axios@1.12.2: + resolution: + { + integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw== + } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + } + + balanced-match@2.0.0: + resolution: + { + integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== + } + + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + } + + baseline-browser-mapping@2.8.8: + resolution: + { + integrity: sha512-be0PUaPsQX/gPWWgFsdD+GFzaoig5PXaUC1xLkQiYdDnANU8sMnHoQd8JhbJQuvTWrWLyeFN9Imb5Qtfvr4RrQ== + } + hasBin: true + + binary-extensions@2.3.0: + resolution: + { + integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + } + engines: { node: '>=8' } + + birpc@2.6.1: + resolution: + { + integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ== + } + + bl@4.1.0: + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + } + + boolbase@1.0.0: + resolution: + { + integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + } + + brace-expansion@1.1.12: + resolution: + { + integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + } + engines: { node: '>=8' } + + browserslist@4.26.2: + resolution: + { + integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A== + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + buffer-from@1.1.2: + resolution: + { + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + } + + buffer@5.7.1: + resolution: + { + integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + } + + bundle-name@4.1.0: + resolution: + { + integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== + } + engines: { node: '>=18' } + + cacheable@2.0.2: + resolution: + { + integrity: sha512-dWjhLx8RWnPsAWVKwW/wI6OJpQ/hSVb1qS0NUif8TR9vRiSwci7Gey8x04kRU9iAF+Rnbtex5Kjjfg/aB5w8Pg== + } + + cachedir@2.3.0: + resolution: + { + integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== + } + engines: { node: '>=6' } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + } + engines: { node: '>= 0.4' } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + } + engines: { node: '>=6' } + + caniuse-lite@1.0.30001745: + resolution: + { + integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ== + } + + cfb@1.2.2: + resolution: + { + integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA== + } + engines: { node: '>=0.8' } + + chalk@2.4.2: + resolution: + { + integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + } + engines: { node: '>=4' } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + } + engines: { node: '>=10' } + + chalk@5.6.2: + resolution: + { + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + chardet@0.7.0: + resolution: + { + integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + } + + chokidar@3.6.0: + resolution: + { + integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + } + engines: { node: '>= 8.10.0' } + + chokidar@4.0.3: + resolution: + { + integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + } + engines: { node: '>= 14.16.0' } + + chownr@3.0.0: + resolution: + { + integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + } + engines: { node: '>=18' } + + cli-cursor@3.1.0: + resolution: + { + integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + } + engines: { node: '>=8' } + + cli-cursor@5.0.0: + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + } + engines: { node: '>=18' } + + cli-spinners@2.9.2: + resolution: + { + integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + } + engines: { node: '>=6' } + + cli-truncate@4.0.0: + resolution: + { + integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== + } + engines: { node: '>=18' } + + cli-width@3.0.0: + resolution: + { + integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + } + engines: { node: '>= 10' } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + } + engines: { node: '>=12' } + + clone@1.0.4: + resolution: + { + integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + } + engines: { node: '>=0.8' } + + codepage@1.15.0: + resolution: + { + integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA== + } + engines: { node: '>=0.8' } + + color-convert@1.9.3: + resolution: + { + integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + } + engines: { node: '>=7.0.0' } + + color-name@1.1.3: + resolution: + { + integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + } + + colord@2.9.3: + resolution: + { + integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + } + + colorette@2.0.20: + resolution: + { + integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + } + + combined-stream@1.0.8: + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + } + engines: { node: '>= 0.8' } + + commander@13.1.0: + resolution: + { + integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== + } + engines: { node: '>=18' } + + commander@2.20.3: + resolution: + { + integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + } + + commitizen@4.3.1: + resolution: + { + integrity: sha512-gwAPAVTy/j5YcOOebcCRIijn+mSjWJC+IYKivTu6aG8Ei/scoXgfsMRnuAk6b0GRste2J4NGxVdMN3ZpfNaVaw== + } + engines: { node: '>= 12' } + hasBin: true + + compare-func@2.0.0: + resolution: + { + integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + } + + compute-scroll-into-view@1.0.20: + resolution: + { + integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg== + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + } + + confbox@0.1.8: + resolution: + { + integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + } + + confbox@0.2.2: + resolution: + { + integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== + } + + conventional-changelog-angular@7.0.0: + resolution: + { + integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ== + } + engines: { node: '>=16' } + + conventional-changelog-conventionalcommits@7.0.2: + resolution: + { + integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w== + } + engines: { node: '>=16' } + + conventional-commit-types@3.0.0: + resolution: + { + integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg== + } + + conventional-commits-parser@5.0.0: + resolution: + { + integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA== + } + engines: { node: '>=16' } + hasBin: true + + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + } + + copy-anything@3.0.5: + resolution: + { + integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w== + } + engines: { node: '>=12.13' } + + core-js@3.45.1: + resolution: + { + integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg== + } + + cosmiconfig-typescript-loader@6.1.0: + resolution: + { + integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g== + } + engines: { node: '>=v18' } + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@9.0.0: + resolution: + { + integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + } + engines: { node: '>=14' } + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + crc-32@1.2.2: + resolution: + { + integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + } + engines: { node: '>=0.8' } + hasBin: true + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + } + engines: { node: '>= 8' } + + crypto-js@4.2.0: + resolution: + { + integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + } + + css-functions-list@3.2.3: + resolution: + { + integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA== + } + engines: { node: '>=12 || >=16' } + + css-tree@3.1.0: + resolution: + { + integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w== + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0 } + + cssesc@3.0.0: + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + } + engines: { node: '>=4' } + hasBin: true + + csstype@3.1.3: + resolution: + { + integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + } + + cz-conventional-changelog@3.3.0: + resolution: + { + integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw== + } + engines: { node: '>= 10' } + + cz-git@1.12.0: + resolution: + { + integrity: sha512-LaZ+8whPPUOo6Y0Zy4nIbf6JOleV3ejp41sT6N4RPKiKKA+ICWf4ueeIlxIO8b6JtdlDxRzHH/EcRji07nDxcg== + } + engines: { node: '>=v12.20.0' } + + d@1.0.2: + resolution: + { + integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== + } + engines: { node: '>=0.12' } + + danmu.js@1.1.13: + resolution: + { + integrity: sha512-knFd0/cB2HA4FFWiA7eB2suc5vCvoHdqio33FyyCSfP7C+1A+zQcTvnvwfxaZhrxsGj4qaQI2I8XiTqedRaVmg== + } + + dargs@8.1.0: + resolution: + { + integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw== + } + engines: { node: '>=12' } + + dayjs@1.11.18: + resolution: + { + integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA== + } + + de-indent@1.0.2: + resolution: + { + integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== + } + + debug@4.4.3: + resolution: + { + integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@0.7.0: + resolution: + { + integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + } + + deep-pick-omit@1.2.1: + resolution: + { + integrity: sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw== + } + + default-browser-id@5.0.0: + resolution: + { + integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== + } + engines: { node: '>=18' } + + default-browser@5.2.1: + resolution: + { + integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== + } + engines: { node: '>=18' } + + defaults@1.0.4: + resolution: + { + integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + } + + define-lazy-prop@2.0.0: + resolution: + { + integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + } + engines: { node: '>=8' } + + define-lazy-prop@3.0.0: + resolution: + { + integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + } + engines: { node: '>=12' } + + defu@6.1.4: + resolution: + { + integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== + } + + delayed-stream@1.0.0: + resolution: + { + integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + } + engines: { node: '>=0.4.0' } + + delegate@3.2.0: + resolution: + { + integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + } + + destr@2.0.5: + resolution: + { + integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== + } + + detect-file@1.0.0: + resolution: + { + integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== + } + engines: { node: '>=0.10.0' } + + detect-indent@6.1.0: + resolution: + { + integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== + } + engines: { node: '>=8' } + + detect-libc@1.0.3: + resolution: + { + integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + } + engines: { node: '>=0.10' } + hasBin: true + + detect-libc@2.1.2: + resolution: + { + integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + } + engines: { node: '>=8' } + + dir-glob@3.0.1: + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + } + engines: { node: '>=8' } + + dom-serializer@2.0.0: + resolution: + { + integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + } + + dom7@3.0.0: + resolution: + { + integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g== + } + + domelementtype@2.3.0: + resolution: + { + integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + } + + domhandler@5.0.3: + resolution: + { + integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + } + engines: { node: '>= 4' } + + domutils@3.2.2: + resolution: + { + integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== + } + + dot-prop@5.3.0: + resolution: + { + integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + } + engines: { node: '>=8' } + + downloadjs@1.4.7: + resolution: + { + integrity: sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q== + } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + } + engines: { node: '>= 0.4' } + + echarts@6.0.0: + resolution: + { + integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ== + } + + electron-to-chromium@1.5.227: + resolution: + { + integrity: sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA== + } + + element-plus@2.11.4: + resolution: + { + integrity: sha512-sLq+Ypd0cIVilv8wGGMEGvzRVBBsRpJjnAS5PsI/1JU1COZXqzH3N1UYMUc/HCdvdjf6dfrBy80Sj7KcACsT7w== + } + peerDependencies: + vue: ^3.2.0 + + emoji-regex@10.5.0: + resolution: + { + integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg== + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + } + + enhanced-resolve@5.18.3: + resolution: + { + integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== + } + engines: { node: '>=10.13.0' } + + entities@4.5.0: + resolution: + { + integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + } + engines: { node: '>=0.12' } + + env-paths@2.2.1: + resolution: + { + integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + } + engines: { node: '>=6' } + + environment@1.1.0: + resolution: + { + integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + } + engines: { node: '>=18' } + + error-ex@1.3.4: + resolution: + { + integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + } + + error-stack-parser-es@0.1.5: + resolution: + { + integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg== + } + + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + } + engines: { node: '>= 0.4' } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + } + engines: { node: '>= 0.4' } + + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + } + engines: { node: '>= 0.4' } + + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + } + engines: { node: '>= 0.4' } + + es5-ext@0.10.64: + resolution: + { + integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== + } + engines: { node: '>=0.10' } + + es6-iterator@2.0.3: + resolution: + { + integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + } + + es6-symbol@3.1.4: + resolution: + { + integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== + } + engines: { node: '>=0.12' } + + esbuild@0.25.10: + resolution: + { + integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ== + } + engines: { node: '>=18' } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + } + engines: { node: '>=6' } + + escape-html@1.0.3: + resolution: + { + integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + } + + escape-string-regexp@1.0.5: + resolution: + { + integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + } + engines: { node: '>=0.8.0' } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + } + engines: { node: '>=10' } + + escape-string-regexp@5.0.0: + resolution: + { + integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + } + engines: { node: '>=12' } + + eslint-config-prettier@9.1.2: + resolution: + { + integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ== + } + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.4: + resolution: + { + integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg== + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-vue@9.33.0: + resolution: + { + integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw== + } + engines: { node: ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-scope@7.2.2: + resolution: + { + integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-scope@8.4.0: + resolution: + { + integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint@9.36.0: + resolution: + { + integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esniff@2.0.1: + resolution: + { + integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + } + engines: { node: '>=0.10' } + + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + espree@9.6.1: + resolution: + { + integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + esquery@1.6.0: + resolution: + { + integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + } + engines: { node: '>=0.10' } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + } + engines: { node: '>=4.0' } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + } + engines: { node: '>=4.0' } + + estree-walker@2.0.2: + resolution: + { + integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + } + engines: { node: '>=0.10.0' } + + event-emitter@0.3.5: + resolution: + { + integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + } + + eventemitter3@4.0.7: + resolution: + { + integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + } + + eventemitter3@5.0.1: + resolution: + { + integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + } + + execa@8.0.1: + resolution: + { + integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + } + engines: { node: '>=16.17' } + + execa@9.6.0: + resolution: + { + integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw== + } + engines: { node: ^18.19.0 || >=20.5.0 } + + expand-tilde@2.0.2: + resolution: + { + integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== + } + engines: { node: '>=0.10.0' } + + exsolve@1.0.7: + resolution: + { + integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw== + } + + ext@1.7.0: + resolution: + { + integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + } + + external-editor@3.1.0: + resolution: + { + integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + } + engines: { node: '>=4' } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + } + + fast-diff@1.3.0: + resolution: + { + integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + } + engines: { node: '>=8.6.0' } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + } + + fast-uri@3.1.0: + resolution: + { + integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + } + + fastest-levenshtein@1.0.16: + resolution: + { + integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + } + engines: { node: '>= 4.9.1' } + + fastq@1.19.1: + resolution: + { + integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + } + + fdir@6.5.0: + resolution: + { + integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + } + engines: { node: '>=12.0.0' } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@3.2.0: + resolution: + { + integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + } + engines: { node: '>=8' } + + figures@6.1.0: + resolution: + { + integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg== + } + engines: { node: '>=18' } + + file-entry-cache@10.1.4: + resolution: + { + integrity: sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA== + } + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + } + engines: { node: '>=16.0.0' } + + file-saver@2.0.5: + resolution: + { + integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + } + engines: { node: '>=8' } + + find-node-modules@2.1.3: + resolution: + { + integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg== + } + + find-root@1.1.0: + resolution: + { + integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + } + engines: { node: '>=10' } + + find-up@7.0.0: + resolution: + { + integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g== + } + engines: { node: '>=18' } + + findup-sync@4.0.0: + resolution: + { + integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== + } + engines: { node: '>= 8' } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + } + engines: { node: '>=16' } + + flat-cache@6.1.14: + resolution: + { + integrity: sha512-ExZSCSV9e7v/Zt7RzCbX57lY2dnPdxzU/h3UE6WJ6NtEMfwBd8jmi1n4otDEUfz+T/R+zxrFDpICFdjhD3H/zw== + } + + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + } + + follow-redirects@1.15.11: + resolution: + { + integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + } + engines: { node: '>=4.0' } + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: + { + integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== + } + engines: { node: '>= 6' } + + frac@1.1.2: + resolution: + { + integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== + } + engines: { node: '>=0.8' } + + fs-extra@10.1.0: + resolution: + { + integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + } + engines: { node: '>=12' } + + fs-extra@11.3.2: + resolution: + { + integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A== + } + engines: { node: '>=14.14' } + + fs-extra@9.1.0: + resolution: + { + integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + } + engines: { node: '>=10' } + + fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + } + + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + } + engines: { node: '>=6.9.0' } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-east-asian-width@1.4.0: + resolution: + { + integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q== + } + engines: { node: '>=18' } + + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + } + engines: { node: '>= 0.4' } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + } + engines: { node: '>= 0.4' } + + get-stream@8.0.1: + resolution: + { + integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + } + engines: { node: '>=16' } + + get-stream@9.0.1: + resolution: + { + integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA== + } + engines: { node: '>=18' } + + get-tsconfig@4.10.1: + resolution: + { + integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ== + } + + git-raw-commits@4.0.0: + resolution: + { + integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ== + } + engines: { node: '>=16' } + hasBin: true + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + } + engines: { node: '>= 6' } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + } + engines: { node: '>=10.13.0' } + + glob@7.2.3: + resolution: + { + integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + } + deprecated: Glob versions prior to v9 are no longer supported + + global-directory@4.0.1: + resolution: + { + integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== + } + engines: { node: '>=18' } + + global-modules@1.0.0: + resolution: + { + integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + } + engines: { node: '>=0.10.0' } + + global-modules@2.0.0: + resolution: + { + integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + } + engines: { node: '>=6' } + + global-prefix@1.0.2: + resolution: + { + integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== + } + engines: { node: '>=0.10.0' } + + global-prefix@3.0.0: + resolution: + { + integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + } + engines: { node: '>=6' } + + globals@13.24.0: + resolution: + { + integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + } + engines: { node: '>=8' } + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + } + engines: { node: '>=18' } + + globals@15.15.0: + resolution: + { + integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== + } + engines: { node: '>=18' } + + globby@11.1.0: + resolution: + { + integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + } + engines: { node: '>=10' } + + globjoin@0.1.4: + resolution: + { + integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== + } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + } + engines: { node: '>= 0.4' } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + } + + graphemer@1.4.0: + resolution: + { + integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + } + + has-flag@3.0.0: + resolution: + { + integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + } + engines: { node: '>=4' } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + } + engines: { node: '>=8' } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + } + engines: { node: '>= 0.4' } + + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + } + engines: { node: '>= 0.4' } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + } + engines: { node: '>= 0.4' } + + he@1.2.0: + resolution: + { + integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + } + hasBin: true + + highlight.js@11.11.1: + resolution: + { + integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w== + } + engines: { node: '>=12.0.0' } + + homedir-polyfill@1.0.3: + resolution: + { + integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + } + engines: { node: '>=0.10.0' } + + hookable@5.5.3: + resolution: + { + integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ== + } + + hookified@1.12.1: + resolution: + { + integrity: sha512-xnKGl+iMIlhrZmGHB729MqlmPoWBznctSQTYCpFKqNsCgimJQmithcW0xSQMMFzYnV2iKUh25alswn6epgxS0Q== + } + + html-tags@3.3.1: + resolution: + { + integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + } + engines: { node: '>=8' } + + html-void-elements@2.0.1: + resolution: + { + integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A== + } + + htmlparser2@8.0.2: + resolution: + { + integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + } + + human-signals@5.0.0: + resolution: + { + integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + } + engines: { node: '>=16.17.0' } + + human-signals@8.0.1: + resolution: + { + integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ== + } + engines: { node: '>=18.18.0' } + + husky@9.1.7: + resolution: + { + integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== + } + engines: { node: '>=18' } + hasBin: true + + i18next@20.6.1: + resolution: + { + integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A== + } + + iconv-lite@0.4.24: + resolution: + { + integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + } + engines: { node: '>=0.10.0' } + + ieee754@1.2.1: + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + } + engines: { node: '>= 4' } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + } + engines: { node: '>= 4' } + + immer@9.0.21: + resolution: + { + integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + } + + immutable@5.1.3: + resolution: + { + integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg== + } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + } + engines: { node: '>=6' } + + import-meta-resolve@4.2.0: + resolution: + { + integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg== + } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + } + engines: { node: '>=0.8.19' } + + inflight@1.0.6: + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + } + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + } + + ini@1.3.8: + resolution: + { + integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + } + + ini@4.1.1: + resolution: + { + integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + inquirer@8.2.5: + resolution: + { + integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== + } + engines: { node: '>=12.0.0' } + + is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + } + + is-binary-path@2.1.0: + resolution: + { + integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + } + engines: { node: '>=8' } + + is-docker@2.2.1: + resolution: + { + integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + } + engines: { node: '>=8' } + hasBin: true + + is-docker@3.0.0: + resolution: + { + integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + hasBin: true + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + } + engines: { node: '>=0.10.0' } + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + } + engines: { node: '>=8' } + + is-fullwidth-code-point@4.0.0: + resolution: + { + integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + } + engines: { node: '>=12' } + + is-fullwidth-code-point@5.1.0: + resolution: + { + integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== + } + engines: { node: '>=18' } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + } + engines: { node: '>=0.10.0' } + + is-hotkey@0.2.0: + resolution: + { + integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw== + } + + is-inside-container@1.0.0: + resolution: + { + integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + } + engines: { node: '>=14.16' } + hasBin: true + + is-interactive@1.0.0: + resolution: + { + integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + } + engines: { node: '>=8' } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + } + engines: { node: '>=0.12.0' } + + is-obj@2.0.0: + resolution: + { + integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + } + engines: { node: '>=8' } + + is-plain-obj@4.1.0: + resolution: + { + integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + } + engines: { node: '>=12' } + + is-plain-object@5.0.0: + resolution: + { + integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + } + engines: { node: '>=0.10.0' } + + is-stream@3.0.0: + resolution: + { + integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + is-stream@4.0.1: + resolution: + { + integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== + } + engines: { node: '>=18' } + + is-text-path@2.0.0: + resolution: + { + integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw== + } + engines: { node: '>=8' } + + is-unicode-supported@0.1.0: + resolution: + { + integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + } + engines: { node: '>=10' } + + is-unicode-supported@2.1.0: + resolution: + { + integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== + } + engines: { node: '>=18' } + + is-url@1.2.4: + resolution: + { + integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + } + + is-utf8@0.2.1: + resolution: + { + integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + } + + is-what@4.1.16: + resolution: + { + integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A== + } + engines: { node: '>=12.13' } + + is-windows@1.0.2: + resolution: + { + integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + } + engines: { node: '>=0.10.0' } + + is-wsl@2.2.0: + resolution: + { + integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + } + engines: { node: '>=8' } + + is-wsl@3.1.0: + resolution: + { + integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + } + engines: { node: '>=16' } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + } + + jiti@2.6.0: + resolution: + { + integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ== + } + hasBin: true + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + } + + js-tokens@9.0.1: + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== + } + + js-yaml@4.1.0: + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + } + hasBin: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + } + engines: { node: '>=6' } + hasBin: true + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + } + + json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + } + + json-schema-traverse@1.0.0: + resolution: + { + integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + } + + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + } + engines: { node: '>=6' } + hasBin: true + + jsonfile@6.2.0: + resolution: + { + integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + } + + jsonparse@1.3.1: + resolution: + { + integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + } + engines: { '0': node >= 0.2.0 } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + } + + keyv@5.5.3: + resolution: + { + integrity: sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A== + } + + kind-of@6.0.3: + resolution: + { + integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + } + engines: { node: '>=0.10.0' } + + known-css-properties@0.36.0: + resolution: + { + integrity: sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA== + } + + known-css-properties@0.37.0: + resolution: + { + integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ== + } + + kolorist@1.8.0: + resolution: + { + integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== + } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + } + engines: { node: '>= 0.8.0' } + + lightningcss-darwin-arm64@1.30.1: + resolution: + { + integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== + } + engines: { node: '>= 12.0.0' } + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: + { + integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== + } + engines: { node: '>= 12.0.0' } + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: + { + integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== + } + engines: { node: '>= 12.0.0' } + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: + { + integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== + } + engines: { node: '>= 12.0.0' } + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: + { + integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== + } + engines: { node: '>= 12.0.0' } + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: + { + integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== + } + engines: { node: '>= 12.0.0' } + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: + { + integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== + } + engines: { node: '>= 12.0.0' } + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.30.1: + resolution: + { + integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== + } + engines: { node: '>= 12.0.0' } + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: + { + integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== + } + engines: { node: '>= 12.0.0' } + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: + { + integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== + } + engines: { node: '>= 12.0.0' } + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: + { + integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== + } + engines: { node: '>= 12.0.0' } + + lilconfig@3.1.3: + resolution: + { + integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + } + engines: { node: '>=14' } + + lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + } + + lint-staged@15.5.2: + resolution: + { + integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w== + } + engines: { node: '>=18.12.0' } + hasBin: true + + listr2@8.3.3: + resolution: + { + integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ== + } + engines: { node: '>=18.0.0' } + + local-pkg@1.1.2: + resolution: + { + integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A== + } + engines: { node: '>=14' } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + } + engines: { node: '>=10' } + + locate-path@7.2.0: + resolution: + { + integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + lodash-es@4.17.21: + resolution: + { + integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + } + + lodash-unified@1.0.3: + resolution: + { + integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ== + } + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash.camelcase@4.3.0: + resolution: + { + integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + } + + lodash.clonedeep@4.5.0: + resolution: + { + integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + } + + lodash.debounce@4.0.8: + resolution: + { + integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + } + + lodash.foreach@4.5.0: + resolution: + { + integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ== + } + + lodash.isequal@4.5.0: + resolution: + { + integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + } + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isplainobject@4.0.6: + resolution: + { + integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + } + + lodash.kebabcase@4.1.1: + resolution: + { + integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== + } + + lodash.map@4.6.0: + resolution: + { + integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q== + } + + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + } + + lodash.mergewith@4.6.2: + resolution: + { + integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== + } + + lodash.snakecase@4.1.1: + resolution: + { + integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== + } + + lodash.startcase@4.4.0: + resolution: + { + integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg== + } + + lodash.throttle@4.1.1: + resolution: + { + integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== + } + + lodash.toarray@4.4.0: + resolution: + { + integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw== + } + + lodash.truncate@4.4.2: + resolution: + { + integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + } + + lodash.uniq@4.5.0: + resolution: + { + integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + } + + lodash.upperfirst@4.3.1: + resolution: + { + integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== + } + + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + } + + log-symbols@4.1.0: + resolution: + { + integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + } + engines: { node: '>=10' } + + log-update@6.1.0: + resolution: + { + integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== + } + engines: { node: '>=18' } + + longest@2.0.1: + resolution: + { + integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q== + } + engines: { node: '>=0.10.0' } + + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + } + + magic-string@0.30.19: + resolution: + { + integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw== + } + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + } + engines: { node: '>= 0.4' } + + mathml-tag-names@2.1.3: + resolution: + { + integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== + } + + mdn-data@2.12.2: + resolution: + { + integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA== + } + + mdn-data@2.24.0: + resolution: + { + integrity: sha512-i97fklrJl03tL1tdRVw0ZfLLvuDsdb6wxL+TrJ+PKkCbLrp2PCu2+OYdCKychIUm19nSM/35S6qz7pJpnXttoA== + } + + memoize-one@6.0.0: + resolution: + { + integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + } + + meow@12.1.1: + resolution: + { + integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw== + } + engines: { node: '>=16.10' } + + meow@13.2.0: + resolution: + { + integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== + } + engines: { node: '>=18' } + + merge-stream@2.0.0: + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + } + engines: { node: '>= 8' } + + merge@2.1.1: + resolution: + { + integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== + } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + } + engines: { node: '>=8.6' } + + mime-db@1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + } + engines: { node: '>= 0.6' } + + mime-match@1.0.2: + resolution: + { + integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg== + } + + mime-types@2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + } + engines: { node: '>= 0.6' } + + mimic-fn@2.1.0: + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + } + engines: { node: '>=6' } + + mimic-fn@4.0.0: + resolution: + { + integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + } + engines: { node: '>=12' } + + mimic-function@5.0.1: + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + } + engines: { node: '>=18' } + + minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + } + + minimatch@9.0.5: + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + } + engines: { node: '>=16 || 14 >=14.17' } + + minimist@1.2.7: + resolution: + { + integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + } + + minimist@1.2.8: + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + } + + minipass@7.1.2: + resolution: + { + integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + } + engines: { node: '>=16 || 14 >=14.17' } + + minizlib@3.1.0: + resolution: + { + integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== + } + engines: { node: '>= 18' } + + mitt@3.0.1: + resolution: + { + integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== + } + + mlly@1.8.0: + resolution: + { + integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g== + } + + mrmime@2.0.1: + resolution: + { + integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ== + } + engines: { node: '>=10' } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + } + + muggle-string@0.4.1: + resolution: + { + integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== + } + + mute-stream@0.0.8: + resolution: + { + integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + } + + namespace-emitter@2.0.1: + resolution: + { + integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g== + } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + nanoid@5.1.6: + resolution: + { + integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg== + } + engines: { node: ^18 || >=20 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + } + + next-tick@1.1.0: + resolution: + { + integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + } + + node-addon-api@7.1.1: + resolution: + { + integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + } + + node-releases@2.0.21: + resolution: + { + integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== + } + + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + } + engines: { node: '>=0.10.0' } + + normalize-wheel-es@1.2.0: + resolution: + { + integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw== + } + + npm-run-path@5.3.0: + resolution: + { + integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + npm-run-path@6.0.0: + resolution: + { + integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA== + } + engines: { node: '>=18' } + + nprogress@0.2.0: + resolution: + { + integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA== + } + + nth-check@2.1.1: + resolution: + { + integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + } + + ohash@2.0.11: + resolution: + { + integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ== + } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + } + + onetime@5.1.2: + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + } + engines: { node: '>=6' } + + onetime@6.0.0: + resolution: + { + integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + } + engines: { node: '>=12' } + + onetime@7.0.0: + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + } + engines: { node: '>=18' } + + open@10.2.0: + resolution: + { + integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA== + } + engines: { node: '>=18' } + + open@8.4.2: + resolution: + { + integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + } + engines: { node: '>=12' } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + } + engines: { node: '>= 0.8.0' } + + ora@5.4.1: + resolution: + { + integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + } + engines: { node: '>=10' } + + os-tmpdir@1.0.2: + resolution: + { + integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + } + engines: { node: '>=0.10.0' } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + } + engines: { node: '>=10' } + + p-limit@4.0.0: + resolution: + { + integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + } + engines: { node: '>=10' } + + p-locate@6.0.0: + resolution: + { + integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + } + engines: { node: '>=6' } + + parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + } + engines: { node: '>=8' } + + parse-ms@4.0.0: + resolution: + { + integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw== + } + engines: { node: '>=18' } + + parse-passwd@1.0.0: + resolution: + { + integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== + } + engines: { node: '>=0.10.0' } + + path-browserify@1.0.1: + resolution: + { + integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + } + engines: { node: '>=8' } + + path-exists@5.0.0: + resolution: + { + integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + path-is-absolute@1.0.1: + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + } + engines: { node: '>=0.10.0' } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + } + engines: { node: '>=8' } + + path-key@4.0.0: + resolution: + { + integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + } + engines: { node: '>=12' } + + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + } + engines: { node: '>=8' } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + } + + perfect-debounce@1.0.0: + resolution: + { + integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA== + } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + } + engines: { node: '>=8.6' } + + picomatch@4.0.3: + resolution: + { + integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + } + engines: { node: '>=12' } + + pidtree@0.6.0: + resolution: + { + integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + } + engines: { node: '>=0.10' } + hasBin: true + + pinia-plugin-persistedstate@4.5.0: + resolution: + { + integrity: sha512-QTkP1xJVyCdr2I2p3AKUZM84/e+IS+HktRxKGAIuDzkyaKKV48mQcYkJFVVDuvTxlI5j6X3oZObpqoVB8JnWpw== + } + peerDependencies: + '@nuxt/kit': '>=3.0.0' + '@pinia/nuxt': '>=0.10.0' + pinia: '>=3.0.0' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@pinia/nuxt': + optional: true + pinia: + optional: true + + pinia@3.0.3: + resolution: + { + integrity: sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA== + } + peerDependencies: + typescript: '>=4.4.4' + vue: ^2.7.0 || ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: + { + integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== + } + + pkg-types@2.3.0: + resolution: + { + integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig== + } + + postcss-html@1.8.0: + resolution: + { + integrity: sha512-5mMeb1TgLWoRKxZ0Xh9RZDfwUUIqRrcxO2uXO+Ezl1N5lqpCiSU5Gk6+1kZediBfBHFtPCdopr2UZ2SgUsKcgQ== + } + engines: { node: ^12 || >=14 } + + postcss-media-query-parser@0.2.3: + resolution: + { + integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== + } + + postcss-resolve-nested-selector@0.1.6: + resolution: + { + integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw== + } + + postcss-safe-parser@6.0.0: + resolution: + { + integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ== + } + engines: { node: '>=12.0' } + peerDependencies: + postcss: ^8.3.3 + + postcss-safe-parser@7.0.1: + resolution: + { + integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A== + } + engines: { node: '>=18.0' } + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: + { + integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A== + } + engines: { node: '>=12.0' } + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@6.1.2: + resolution: + { + integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + } + engines: { node: '>=4' } + + postcss-selector-parser@7.1.0: + resolution: + { + integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== + } + engines: { node: '>=4' } + + postcss-sorting@8.0.2: + resolution: + { + integrity: sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q== + } + peerDependencies: + postcss: ^8.4.20 + + postcss-value-parser@4.2.0: + resolution: + { + integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + } + engines: { node: ^10 || ^12 || >=14 } + + preact@10.27.2: + resolution: + { + integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg== + } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + } + engines: { node: '>= 0.8.0' } + + prettier-linter-helpers@1.0.0: + resolution: + { + integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + } + engines: { node: '>=6.0.0' } + + prettier@3.6.2: + resolution: + { + integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== + } + engines: { node: '>=14' } + hasBin: true + + pretty-ms@9.3.0: + resolution: + { + integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ== + } + engines: { node: '>=18' } + + prismjs@1.30.0: + resolution: + { + integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw== + } + engines: { node: '>=6' } + + proxy-from-env@1.1.0: + resolution: + { + integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + } + engines: { node: '>=6' } + + qrcode.vue@3.6.0: + resolution: + { + integrity: sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg== + } + peerDependencies: + vue: ^3.0.0 + + quansync@0.2.11: + resolution: + { + integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA== + } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + } + + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + } + engines: { node: '>= 6' } + + readdirp@3.6.0: + resolution: + { + integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + } + engines: { node: '>=8.10.0' } + + readdirp@4.1.2: + resolution: + { + integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + } + engines: { node: '>= 14.18.0' } + + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + } + engines: { node: '>=0.10.0' } + + require-from-string@2.0.2: + resolution: + { + integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + } + engines: { node: '>=0.10.0' } + + resolve-dir@1.0.1: + resolution: + { + integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== + } + engines: { node: '>=0.10.0' } + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + } + engines: { node: '>=4' } + + resolve-from@5.0.0: + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + } + engines: { node: '>=8' } + + resolve-pkg-maps@1.0.0: + resolution: + { + integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + } + + restore-cursor@3.1.0: + resolution: + { + integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + } + engines: { node: '>=8' } + + restore-cursor@5.1.0: + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + } + engines: { node: '>=18' } + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + + rfdc@1.4.1: + resolution: + { + integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + } + + rollup-plugin-visualizer@5.14.0: + resolution: + { + integrity: sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA== + } + engines: { node: '>=18' } + hasBin: true + peerDependencies: + rolldown: 1.x + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + + rollup@4.52.3: + resolution: + { + integrity: sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A== + } + engines: { node: '>=18.0.0', npm: '>=8.0.0' } + hasBin: true + + run-applescript@7.1.0: + resolution: + { + integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== + } + engines: { node: '>=18' } + + run-async@2.4.1: + resolution: + { + integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + } + engines: { node: '>=0.12.0' } + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + } + + rxjs@7.8.2: + resolution: + { + integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + } + + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + } + + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + } + + sass@1.93.2: + resolution: + { + integrity: sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg== + } + engines: { node: '>=14.0.0' } + hasBin: true + + scroll-into-view-if-needed@2.2.31: + resolution: + { + integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA== + } + + scule@1.3.0: + resolution: + { + integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g== + } + + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + } + hasBin: true + + semver@7.7.2: + resolution: + { + integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + } + engines: { node: '>=10' } + hasBin: true + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + } + engines: { node: '>=8' } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + } + engines: { node: '>=8' } + + signal-exit@3.0.7: + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + } + + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + } + engines: { node: '>=14' } + + sirv@3.0.2: + resolution: + { + integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g== + } + engines: { node: '>=18' } + + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + } + engines: { node: '>=8' } + + slate-history@0.66.0: + resolution: + { + integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng== + } + peerDependencies: + slate: '>=0.65.3' + + slate@0.72.8: + resolution: + { + integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw== + } + + slice-ansi@4.0.0: + resolution: + { + integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + } + engines: { node: '>=10' } + + slice-ansi@5.0.0: + resolution: + { + integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + } + engines: { node: '>=12' } + + slice-ansi@7.1.2: + resolution: + { + integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w== + } + engines: { node: '>=18' } + + snabbdom@3.6.2: + resolution: + { + integrity: sha512-ig5qOnCDbugFntKi6c7Xlib8bA6xiJVk8O+WdFrV3wxbMqeHO0hXFQC4nAhPVWfZfi8255lcZkNhtIBINCc4+Q== + } + engines: { node: '>=12.17.0' } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + } + engines: { node: '>=0.10.0' } + + source-map-support@0.5.21: + resolution: + { + integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + } + + source-map@0.6.1: + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + } + engines: { node: '>=0.10.0' } + + source-map@0.7.6: + resolution: + { + integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== + } + engines: { node: '>= 12' } + + speakingurl@14.0.1: + resolution: + { + integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ== + } + engines: { node: '>=0.10.0' } + + split2@4.2.0: + resolution: + { + integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + } + engines: { node: '>= 10.x' } + + ssf@0.11.2: + resolution: + { + integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== + } + engines: { node: '>=0.8' } + + ssr-window@3.0.0: + resolution: + { + integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA== + } + + string-argv@0.3.2: + resolution: + { + integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + } + engines: { node: '>=0.6.19' } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + } + engines: { node: '>=8' } + + string-width@7.2.0: + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + } + engines: { node: '>=18' } + + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + } + engines: { node: '>=8' } + + strip-ansi@7.1.2: + resolution: + { + integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + } + engines: { node: '>=12' } + + strip-bom@4.0.0: + resolution: + { + integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + } + engines: { node: '>=8' } + + strip-final-newline@3.0.0: + resolution: + { + integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + } + engines: { node: '>=12' } + + strip-final-newline@4.0.0: + resolution: + { + integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw== + } + engines: { node: '>=18' } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + } + engines: { node: '>=8' } + + strip-literal@3.1.0: + resolution: + { + integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg== + } + + stylelint-config-html@1.1.0: + resolution: + { + integrity: sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ== + } + engines: { node: ^12 || >=14 } + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-recess-order@4.6.0: + resolution: + { + integrity: sha512-V76fhv3YtcNXh/hyAuAdSzi5FmcrG54Mp2AThJ3D/PTMTSYzUPd7GIhP6z9mTqnRhmkk6YTfcu/JWB8h+Yrcaw== + } + peerDependencies: + stylelint: '>=15' + + stylelint-config-recommended-scss@14.1.0: + resolution: + { + integrity: sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg== + } + engines: { node: '>=18.12.0' } + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.6.1 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-recommended-vue@1.6.1: + resolution: + { + integrity: sha512-lLW7hTIMBiTfjenGuDq2kyHA6fBWd/+Df7MO4/AWOxiFeXP9clbpKgg27kHfwA3H7UNMGC7aeP3mNlZB5LMmEQ== + } + engines: { node: ^12 || >=14 } + peerDependencies: + postcss-html: ^1.0.0 + stylelint: '>=14.0.0' + + stylelint-config-recommended@14.0.1: + resolution: + { + integrity: sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg== + } + engines: { node: '>=18.12.0' } + peerDependencies: + stylelint: ^16.1.0 + + stylelint-config-recommended@17.0.0: + resolution: + { + integrity: sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA== + } + engines: { node: '>=18.12.0' } + peerDependencies: + stylelint: ^16.23.0 + + stylelint-config-standard@36.0.1: + resolution: + { + integrity: sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw== + } + engines: { node: '>=18.12.0' } + peerDependencies: + stylelint: ^16.1.0 + + stylelint-order@6.0.4: + resolution: + { + integrity: sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA== + } + peerDependencies: + stylelint: ^14.0.0 || ^15.0.0 || ^16.0.1 + + stylelint-scss@6.12.1: + resolution: + { + integrity: sha512-UJUfBFIvXfly8WKIgmqfmkGKPilKB4L5j38JfsDd+OCg2GBdU0vGUV08Uw82tsRZzd4TbsUURVVNGeOhJVF7pA== + } + engines: { node: '>=18.12.0' } + peerDependencies: + stylelint: ^16.0.2 + + stylelint@16.24.0: + resolution: + { + integrity: sha512-7ksgz3zJaSbTUGr/ujMXvLVKdDhLbGl3R/3arNudH7z88+XZZGNLMTepsY28WlnvEFcuOmUe7fg40Q3lfhOfSQ== + } + engines: { node: '>=18.12.0' } + hasBin: true + + superjson@2.2.2: + resolution: + { + integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q== + } + engines: { node: '>=16' } + + supports-color@5.5.0: + resolution: + { + integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + } + engines: { node: '>=4' } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + } + engines: { node: '>=8' } + + supports-hyperlinks@3.2.0: + resolution: + { + integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig== + } + engines: { node: '>=14.18' } + + svg-tags@1.0.0: + resolution: + { + integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== + } + + synckit@0.11.11: + resolution: + { + integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw== + } + engines: { node: ^14.18.0 || >=16.0.0 } + + table@6.9.0: + resolution: + { + integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== + } + engines: { node: '>=10.0.0' } + + tailwindcss@4.1.14: + resolution: + { + integrity: sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA== + } + + tapable@2.3.0: + resolution: + { + integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== + } + engines: { node: '>=6' } + + tar@7.5.1: + resolution: + { + integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g== + } + engines: { node: '>=18' } + + terser@5.44.0: + resolution: + { + integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w== + } + engines: { node: '>=10' } + hasBin: true + + text-extensions@2.4.0: + resolution: + { + integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g== + } + engines: { node: '>=8' } + + through@2.3.8: + resolution: + { + integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + } + + tiny-warning@1.0.3: + resolution: + { + integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + } + + tinyexec@1.0.1: + resolution: + { + integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw== + } + + tinyglobby@0.2.15: + resolution: + { + integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + } + engines: { node: '>=12.0.0' } + + tmp@0.0.33: + resolution: + { + integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + } + engines: { node: '>=0.6.0' } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + } + engines: { node: '>=8.0' } + + totalist@3.0.1: + resolution: + { + integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== + } + engines: { node: '>=6' } + + ts-api-utils@2.1.0: + resolution: + { + integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + } + engines: { node: '>=18.12' } + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.3.0: + resolution: + { + integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + } + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + } + + tsx@4.20.6: + resolution: + { + integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg== + } + engines: { node: '>=18.0.0' } + hasBin: true + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + } + engines: { node: '>= 0.8.0' } + + type-fest@0.20.2: + resolution: + { + integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + } + engines: { node: '>=10' } + + type-fest@0.21.3: + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + } + engines: { node: '>=10' } + + type@2.7.3: + resolution: + { + integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== + } + + typescript-eslint@8.44.1: + resolution: + { + integrity: sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg== + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.6.3: + resolution: + { + integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== + } + engines: { node: '>=14.17' } + hasBin: true + + ufo@1.6.1: + resolution: + { + integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== + } + + undici-types@7.14.0: + resolution: + { + integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA== + } + + unicorn-magic@0.1.0: + resolution: + { + integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== + } + engines: { node: '>=18' } + + unicorn-magic@0.3.0: + resolution: + { + integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA== + } + engines: { node: '>=18' } + + unimport@5.4.0: + resolution: + { + integrity: sha512-g/OLFZR2mEfqbC6NC9b2225eCJGvufxq34mj6kM3OmI5gdSL0qyqtnv+9qmsGpAmnzSl6x0IWZj4W+8j2hLkMA== + } + engines: { node: '>=18.12.0' } + + universalify@2.0.1: + resolution: + { + integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + } + engines: { node: '>= 10.0.0' } + + unplugin-auto-import@20.2.0: + resolution: + { + integrity: sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug== + } + engines: { node: '>=14' } + peerDependencies: + '@nuxt/kit': ^4.0.0 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + + unplugin-element-plus@0.10.0: + resolution: + { + integrity: sha512-oRSW0x6U58xBOWKy8TcoVZNA8ElIpfp3TUJRLQI6ey/E9PpjHl9/deeTAZNt8D57Li4OA4pCJtM6p2cb4Ff4ZA== + } + engines: { node: '>=18.12.0' } + + unplugin-utils@0.2.5: + resolution: + { + integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg== + } + engines: { node: '>=18.12.0' } + + unplugin-utils@0.3.0: + resolution: + { + integrity: sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg== + } + engines: { node: '>=20.19.0' } + + unplugin-vue-components@29.1.0: + resolution: + { + integrity: sha512-z/9ACPXth199s9aCTCdKZAhe5QGOpvzJYP+Hkd0GN1/PpAmsu+W3UlRY3BJAewPqQxh5xi56+Og6mfiCV1Jzpg== + } + engines: { node: '>=14' } + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 || ^4.0.0 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + + unplugin@2.3.10: + resolution: + { + integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw== + } + engines: { node: '>=18.12.0' } + + update-browserslist-db@1.1.3: + resolution: + { + integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + } + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + } + + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + } + + vite-hot-client@2.1.0: + resolution: + { + integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ== + } + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite-plugin-compression@0.5.1: + resolution: + { + integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg== + } + peerDependencies: + vite: '>=2.0.0' + + vite-plugin-inspect@0.8.9: + resolution: + { + integrity: sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A== + } + engines: { node: '>=14' } + peerDependencies: + '@nuxt/kit': '*' + vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + vite-plugin-vue-devtools@7.7.7: + resolution: + { + integrity: sha512-d0fIh3wRcgSlr4Vz7bAk4va1MkdqhQgj9ANE/rBhsAjOnRfTLs2ocjFMvSUOsv6SRRXU9G+VM7yMgqDb6yI4iQ== + } + engines: { node: '>=v14.21.3' } + peerDependencies: + vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite-plugin-vue-inspector@5.3.2: + resolution: + { + integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q== + } + peerDependencies: + vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite@7.1.7: + resolution: + { + integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA== + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: + { + integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== + } + + vue-demi@0.14.10: + resolution: + { + integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg== + } + engines: { node: '>=12' } + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-draggable-plus@0.6.0: + resolution: + { + integrity: sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw== + } + peerDependencies: + '@types/sortablejs': ^1.15.0 + '@vue/composition-api': '*' + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-eslint-parser@9.4.3: + resolution: + { + integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg== + } + engines: { node: ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: '>=6.0.0' + + vue-i18n@9.14.5: + resolution: + { + integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g== + } + engines: { node: '>= 16' } + peerDependencies: + vue: ^3.0.0 + + vue-img-cutter@3.0.7: + resolution: + { + integrity: sha512-fNw3kimawg9XVXDZCw2bI74NI+Jq+H42wjymatZVVSY46wuBty6LbQsu4GeVfo/yzpS9AHY0tzckpYzX3D2fmA== + } + + vue-router@4.5.1: + resolution: + { + integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw== + } + peerDependencies: + vue: ^3.2.0 + + vue-tsc@2.1.10: + resolution: + { + integrity: sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA== + } + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.22: + resolution: + { + integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ== + } + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + wcwidth@1.0.1: + resolution: + { + integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + } + + webpack-virtual-modules@0.6.2: + resolution: + { + integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== + } + + which@1.3.1: + resolution: + { + integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + } + hasBin: true + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + } + engines: { node: '>= 8' } + hasBin: true + + wildcard@1.1.2: + resolution: + { + integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng== + } + + wmf@1.0.2: + resolution: + { + integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== + } + engines: { node: '>=0.8' } + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + } + engines: { node: '>=0.10.0' } + + word@0.3.0: + resolution: + { + integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== + } + engines: { node: '>=0.8' } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + } + engines: { node: '>=10' } + + wrap-ansi@9.0.2: + resolution: + { + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== + } + engines: { node: '>=18' } + + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + } + + write-file-atomic@5.0.1: + resolution: + { + integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + wsl-utils@0.1.0: + resolution: + { + integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw== + } + engines: { node: '>=18' } + + xgplayer-subtitles@3.0.23: + resolution: + { + integrity: sha512-deGdV75giVzfTTdG9XATmji39NHwKTpEelWt2rRx/RyXGgU2bQFp0Ft7yWaK2Uu8A/WVrP5fpxEAj4MstREMkQ== + } + peerDependencies: + core-js: '>=3.12.1' + + xgplayer@3.0.23: + resolution: + { + integrity: sha512-Bn3zQfMMAZimlVG9EeIDybMcklc+6FH8Sv47KpTq4K6ofCzyhPG/KenxailDedlHmxjb5B2o+240TpJtMQ3oJA== + } + peerDependencies: + core-js: '>=3.12.1' + + xlsx@0.18.5: + resolution: + { + integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ== + } + engines: { node: '>=0.8' } + hasBin: true + + xml-name-validator@4.0.0: + resolution: + { + integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + } + engines: { node: '>=12' } + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + } + engines: { node: '>=10' } + + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + } + + yallist@5.0.0: + resolution: + { + integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== + } + engines: { node: '>=18' } + + yaml@2.8.1: + resolution: + { + integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== + } + engines: { node: '>= 14.6' } + hasBin: true + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + } + engines: { node: '>=12' } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + } + engines: { node: '>=12' } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + } + engines: { node: '>=10' } + + yocto-queue@1.2.1: + resolution: + { + integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg== + } + engines: { node: '>=12.20' } + + yoctocolors@2.1.2: + resolution: + { + integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug== + } + engines: { node: '>=18' } + + zrender@6.0.0: + resolution: + { + integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg== + } + +snapshots: + '@antfu/utils@0.7.10': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.4': {} + + '@babel/core@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.4 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.4 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.26.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.4 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.27.1': + dependencies: + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.4 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.4) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.4': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@babel/traverse@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@cacheable/memoize@2.0.2': + dependencies: + '@cacheable/utils': 2.0.2 + + '@cacheable/memory@2.0.2': + dependencies: + '@cacheable/memoize': 2.0.2 + '@cacheable/utils': 2.0.2 + '@keyv/bigmap': 1.0.2 + hookified: 1.12.1 + keyv: 5.5.3 + + '@cacheable/utils@2.0.2': {} + + '@commitlint/cli@19.8.1(@types/node@24.8.1)(typescript@5.6.3)': + dependencies: + '@commitlint/format': 19.8.1 + '@commitlint/lint': 19.8.1 + '@commitlint/load': 19.8.1(@types/node@24.8.1)(typescript@5.6.3) + '@commitlint/read': 19.8.1 + '@commitlint/types': 19.8.1 + tinyexec: 1.0.1 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/config-conventional@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + conventional-changelog-conventionalcommits: 7.0.2 + + '@commitlint/config-validator@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + ajv: 8.17.1 + + '@commitlint/config-validator@20.0.0': + dependencies: + '@commitlint/types': 20.0.0 + ajv: 8.17.1 + optional: true + + '@commitlint/ensure@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + + '@commitlint/execute-rule@19.8.1': {} + + '@commitlint/execute-rule@20.0.0': + optional: true + + '@commitlint/format@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + chalk: 5.6.2 + + '@commitlint/is-ignored@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + semver: 7.7.2 + + '@commitlint/lint@19.8.1': + dependencies: + '@commitlint/is-ignored': 19.8.1 + '@commitlint/parse': 19.8.1 + '@commitlint/rules': 19.8.1 + '@commitlint/types': 19.8.1 + + '@commitlint/load@19.8.1(@types/node@24.8.1)(typescript@5.6.3)': + dependencies: + '@commitlint/config-validator': 19.8.1 + '@commitlint/execute-rule': 19.8.1 + '@commitlint/resolve-extends': 19.8.1 + '@commitlint/types': 19.8.1 + chalk: 5.6.2 + cosmiconfig: 9.0.0(typescript@5.6.3) + cosmiconfig-typescript-loader: 6.1.0(@types/node@24.8.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/load@20.0.0(@types/node@24.8.1)(typescript@5.6.3)': + dependencies: + '@commitlint/config-validator': 20.0.0 + '@commitlint/execute-rule': 20.0.0 + '@commitlint/resolve-extends': 20.0.0 + '@commitlint/types': 20.0.0 + chalk: 5.6.2 + cosmiconfig: 9.0.0(typescript@5.6.3) + cosmiconfig-typescript-loader: 6.1.0(@types/node@24.8.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - '@types/node' + - typescript + optional: true + + '@commitlint/message@19.8.1': {} + + '@commitlint/parse@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + conventional-changelog-angular: 7.0.0 + conventional-commits-parser: 5.0.0 + + '@commitlint/read@19.8.1': + dependencies: + '@commitlint/top-level': 19.8.1 + '@commitlint/types': 19.8.1 + git-raw-commits: 4.0.0 + minimist: 1.2.8 + tinyexec: 1.0.1 + + '@commitlint/resolve-extends@19.8.1': + dependencies: + '@commitlint/config-validator': 19.8.1 + '@commitlint/types': 19.8.1 + global-directory: 4.0.1 + import-meta-resolve: 4.2.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + + '@commitlint/resolve-extends@20.0.0': + dependencies: + '@commitlint/config-validator': 20.0.0 + '@commitlint/types': 20.0.0 + global-directory: 4.0.1 + import-meta-resolve: 4.2.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + optional: true + + '@commitlint/rules@19.8.1': + dependencies: + '@commitlint/ensure': 19.8.1 + '@commitlint/message': 19.8.1 + '@commitlint/to-lines': 19.8.1 + '@commitlint/types': 19.8.1 + + '@commitlint/to-lines@19.8.1': {} + + '@commitlint/top-level@19.8.1': + dependencies: + find-up: 7.0.0 + + '@commitlint/types@19.8.1': + dependencies: + '@types/conventional-commits-parser': 5.0.1 + chalk: 5.6.2 + + '@commitlint/types@20.0.0': + dependencies: + '@types/conventional-commits-parser': 5.0.1 + chalk: 5.6.2 + optional: true + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + dependencies: + postcss-selector-parser: 7.1.0 + + '@ctrl/tinycolor@3.6.1': {} + + '@dual-bundle/import-meta-resolve@4.2.1': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.22(typescript@5.6.3))': + dependencies: + vue: 3.5.22(typescript@5.6.3) + + '@esbuild/aix-ppc64@0.25.10': + optional: true + + '@esbuild/android-arm64@0.25.10': + optional: true + + '@esbuild/android-arm@0.25.10': + optional: true + + '@esbuild/android-x64@0.25.10': + optional: true + + '@esbuild/darwin-arm64@0.25.10': + optional: true + + '@esbuild/darwin-x64@0.25.10': + optional: true + + '@esbuild/freebsd-arm64@0.25.10': + optional: true + + '@esbuild/freebsd-x64@0.25.10': + optional: true + + '@esbuild/linux-arm64@0.25.10': + optional: true + + '@esbuild/linux-arm@0.25.10': + optional: true + + '@esbuild/linux-ia32@0.25.10': + optional: true + + '@esbuild/linux-loong64@0.25.10': + optional: true + + '@esbuild/linux-mips64el@0.25.10': + optional: true + + '@esbuild/linux-ppc64@0.25.10': + optional: true + + '@esbuild/linux-riscv64@0.25.10': + optional: true + + '@esbuild/linux-s390x@0.25.10': + optional: true + + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': + optional: true + + '@esbuild/netbsd-x64@0.25.10': + optional: true + + '@esbuild/openbsd-arm64@0.25.10': + optional: true + + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': + optional: true + + '@esbuild/sunos-x64@0.25.10': + optional: true + + '@esbuild/win32-arm64@0.25.10': + optional: true + + '@esbuild/win32-ia32@0.25.10': + optional: true + + '@esbuild/win32-x64@0.25.10': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.0))': + dependencies: + eslint: 9.36.0(jiti@2.6.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.36.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/utils@0.2.10': {} + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@iconify/types@2.0.0': {} + + '@iconify/vue@5.0.0(vue@3.5.22(typescript@5.6.3))': + dependencies: + '@iconify/types': 2.0.0 + vue: 3.5.22(typescript@5.6.3) + + '@intlify/core-base@9.14.5': + dependencies: + '@intlify/message-compiler': 9.14.5 + '@intlify/shared': 9.14.5 + + '@intlify/message-compiler@9.14.5': + dependencies: + '@intlify/shared': 9.14.5 + source-map-js: 1.2.1 + + '@intlify/shared@9.14.5': {} + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@keyv/bigmap@1.0.2': + dependencies: + hookified: 1.12.1 + + '@keyv/serialize@1.1.1': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@pkgr/core@0.2.9': {} + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rollup/pluginutils@5.3.0(rollup@4.52.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.52.3 + + '@rollup/rollup-android-arm-eabi@4.52.3': + optional: true + + '@rollup/rollup-android-arm64@4.52.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.3': + optional: true + + '@rollup/rollup-darwin-x64@4.52.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.3': + optional: true + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@sxzz/popperjs-es@2.11.7': {} + + '@tailwindcss/node@4.1.14': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.0 + lightningcss: 1.30.1 + magic-string: 0.30.19 + source-map-js: 1.2.1 + tailwindcss: 4.1.14 + + '@tailwindcss/oxide-android-arm64@4.1.14': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.14': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.14': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.14': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.14': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + optional: true + + '@tailwindcss/oxide@4.1.14': + dependencies: + detect-libc: 2.1.2 + tar: 7.5.1 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-x64': 4.1.14 + '@tailwindcss/oxide-freebsd-x64': 4.1.14 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.14 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.14 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-x64-musl': 4.1.14 + '@tailwindcss/oxide-wasm32-wasi': 4.1.14 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 + + '@tailwindcss/vite@4.1.14(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': + dependencies: + '@tailwindcss/node': 4.1.14 + '@tailwindcss/oxide': 4.1.14 + tailwindcss: 4.1.14 + vite: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + + '@transloadit/prettier-bytes@0.0.7': {} + + '@types/conventional-commits-parser@5.0.1': + dependencies: + '@types/node': 24.8.1 + + '@types/estree@1.0.8': {} + + '@types/event-emitter@0.3.5': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.20 + + '@types/lodash@4.17.20': {} + + '@types/node@24.8.1': + dependencies: + undici-types: 7.14.0 + + '@types/sortablejs@1.15.8': {} + + '@types/web-bluetooth@0.0.16': {} + + '@types/web-bluetooth@0.0.21': {} + + '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.44.1 + '@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.44.1 + eslint: 9.36.0(jiti@2.6.0) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.44.1 + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.44.1 + debug: 4.4.3 + eslint: 9.36.0(jiti@2.6.0) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.44.1(typescript@5.6.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.6.3) + '@typescript-eslint/types': 8.44.1 + debug: 4.4.3 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.44.1': + dependencies: + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/visitor-keys': 8.44.1 + + '@typescript-eslint/tsconfig-utils@8.44.1(typescript@5.6.3)': + dependencies: + typescript: 5.6.3 + + '@typescript-eslint/type-utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.6.3) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + debug: 4.4.3 + eslint: 9.36.0(jiti@2.6.0) + ts-api-utils: 2.1.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.44.1': {} + + '@typescript-eslint/typescript-estree@8.44.1(typescript@5.6.3)': + dependencies: + '@typescript-eslint/project-service': 8.44.1(typescript@5.6.3) + '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.6.3) + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/visitor-keys': 8.44.1 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + '@typescript-eslint/scope-manager': 8.44.1 + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.6.3) + eslint: 9.36.0(jiti@2.6.0) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.44.1': + dependencies: + '@typescript-eslint/types': 8.44.1 + eslint-visitor-keys: 4.2.1 + + '@uppy/companion-client@2.2.2': + dependencies: + '@uppy/utils': 4.1.3 + namespace-emitter: 2.0.1 + + '@uppy/core@2.3.4': + dependencies: + '@transloadit/prettier-bytes': 0.0.7 + '@uppy/store-default': 2.1.1 + '@uppy/utils': 4.1.3 + lodash.throttle: 4.1.1 + mime-match: 1.0.2 + namespace-emitter: 2.0.1 + nanoid: 3.3.11 + preact: 10.27.2 + + '@uppy/store-default@2.1.1': {} + + '@uppy/utils@4.1.3': + dependencies: + lodash.throttle: 4.1.1 + + '@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4)': + dependencies: + '@uppy/companion-client': 2.2.2 + '@uppy/core': 2.3.4 + '@uppy/utils': 4.1.3 + nanoid: 3.3.11 + + '@vitejs/plugin-vue@6.0.1(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.6.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vue: 3.5.22(typescript@5.6.3) + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.4)': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.4) + '@vue/shared': 3.5.22 + optionalDependencies: + '@babel/core': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.28.4)': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.28.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/parser': 7.28.4 + '@vue/compiler-sfc': 3.5.22 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/shared': 3.5.22 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.22': + dependencies: + '@vue/compiler-core': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/compiler-sfc@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/compiler-core': 3.5.22 + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + estree-walker: 2.0.2 + magic-string: 0.30.19 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.22': + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.4': {} + + '@vue/devtools-api@7.7.7': + dependencies: + '@vue/devtools-kit': 7.7.7 + + '@vue/devtools-core@7.7.7(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.6.3))': + dependencies: + '@vue/devtools-kit': 7.7.7 + '@vue/devtools-shared': 7.7.7 + mitt: 3.0.1 + nanoid: 5.1.6 + pathe: 2.0.3 + vite-hot-client: 2.1.0(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + vue: 3.5.22(typescript@5.6.3) + transitivePeerDependencies: + - vite + + '@vue/devtools-kit@7.7.7': + dependencies: + '@vue/devtools-shared': 7.7.7 + birpc: 2.6.1 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@7.7.7': + dependencies: + rfdc: 1.4.1 + + '@vue/language-core@2.1.10(typescript@5.6.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.22 + alien-signals: 0.2.2 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.6.3 + + '@vue/reactivity@3.5.22': + dependencies: + '@vue/shared': 3.5.22 + + '@vue/runtime-core@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/runtime-dom@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/runtime-core': 3.5.22 + '@vue/shared': 3.5.22 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.22(vue@3.5.22(typescript@5.6.3))': + dependencies: + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + vue: 3.5.22(typescript@5.6.3) + + '@vue/shared@3.5.22': {} + + '@vueuse/core@13.9.0(vue@3.5.22(typescript@5.6.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 13.9.0 + '@vueuse/shared': 13.9.0(vue@3.5.22(typescript@5.6.3)) + vue: 3.5.22(typescript@5.6.3) + + '@vueuse/core@9.13.0(vue@3.5.22(typescript@5.6.3))': + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.13.0 + '@vueuse/shared': 9.13.0(vue@3.5.22(typescript@5.6.3)) + vue-demi: 0.14.10(vue@3.5.22(typescript@5.6.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@vueuse/metadata@13.9.0': {} + + '@vueuse/metadata@9.13.0': {} + + '@vueuse/shared@13.9.0(vue@3.5.22(typescript@5.6.3))': + dependencies: + vue: 3.5.22(typescript@5.6.3) + + '@vueuse/shared@9.13.0(vue@3.5.22(typescript@5.6.3))': + dependencies: + vue-demi: 0.14.10(vue@3.5.22(typescript@5.6.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + dom7: 3.0.0 + is-url: 1.2.4 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.2 + + '@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + dom7: 3.0.0 + prismjs: 1.30.0 + slate: 0.72.8 + snabbdom: 3.6.2 + + '@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2)': + dependencies: + '@types/event-emitter': 0.3.5 + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + dom7: 3.0.0 + event-emitter: 0.3.5 + html-void-elements: 2.0.1 + i18next: 20.6.1 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + scroll-into-view-if-needed: 2.2.31 + slate: 0.72.8 + slate-history: 0.66.0(slate@0.72.8) + snabbdom: 3.6.2 + + '@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.22(typescript@5.6.3))': + dependencies: + '@wangeditor/editor': 5.1.23 + vue: 3.5.22(typescript@5.6.3) + + '@wangeditor/editor@5.1.23': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2) + '@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + '@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.2) + '@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + dom7: 3.0.0 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.2 + + '@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.2)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + dom7: 3.0.0 + slate: 0.72.8 + snabbdom: 3.6.2 + + '@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + dom7: 3.0.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.2 + + '@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.2)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + dom7: 3.0.0 + lodash.foreach: 4.5.0 + slate: 0.72.8 + snabbdom: 3.6.2 + + '@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.2) + dom7: 3.0.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.2 + + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + adler-32@1.3.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + alien-signals@0.2.2: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@7.1.1: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + array-ify@1.0.0: {} + + array-union@2.1.0: {} + + astral-regex@2.0.0: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + axios@1.12.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + balanced-match@2.0.0: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.8.8: {} + + binary-extensions@2.3.0: {} + + birpc@2.6.1: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.26.2: + dependencies: + baseline-browser-mapping: 2.8.8 + caniuse-lite: 1.0.30001745 + electron-to-chromium: 1.5.227 + node-releases: 2.0.21 + update-browserslist-db: 1.1.3(browserslist@4.26.2) + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + cacheable@2.0.2: + dependencies: + '@cacheable/memoize': 2.0.2 + '@cacheable/memory': 2.0.2 + '@cacheable/utils': 2.0.2 + hookified: 1.12.1 + keyv: 5.5.3 + + cachedir@2.3.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001745: {} + + cfb@1.2.2: + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chardet@0.7.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chownr@3.0.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cli-width@3.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + codepage@1.15.0: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + colord@2.9.3: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@13.1.0: {} + + commander@2.20.3: {} + + commitizen@4.3.1(@types/node@24.8.1)(typescript@5.6.3): + dependencies: + cachedir: 2.3.0 + cz-conventional-changelog: 3.3.0(@types/node@24.8.1)(typescript@5.6.3) + dedent: 0.7.0 + detect-indent: 6.1.0 + find-node-modules: 2.1.3 + find-root: 1.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + inquirer: 8.2.5 + is-utf8: 0.2.1 + lodash: 4.17.21 + minimist: 1.2.7 + strip-bom: 4.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + conventional-changelog-angular@7.0.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@7.0.2: + dependencies: + compare-func: 2.0.0 + + conventional-commit-types@3.0.0: {} + + conventional-commits-parser@5.0.0: + dependencies: + JSONStream: 1.3.5 + is-text-path: 2.0.0 + meow: 12.1.1 + split2: 4.2.0 + + convert-source-map@2.0.0: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + core-js@3.45.1: {} + + cosmiconfig-typescript-loader@6.1.0(@types/node@24.8.1)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3): + dependencies: + '@types/node': 24.8.1 + cosmiconfig: 9.0.0(typescript@5.6.3) + jiti: 2.6.0 + typescript: 5.6.3 + + cosmiconfig@9.0.0(typescript@5.6.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.6.3 + + crc-32@1.2.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypto-js@4.2.0: {} + + css-functions-list@3.2.3: {} + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + cz-conventional-changelog@3.3.0(@types/node@24.8.1)(typescript@5.6.3): + dependencies: + chalk: 2.4.2 + commitizen: 4.3.1(@types/node@24.8.1)(typescript@5.6.3) + conventional-commit-types: 3.0.0 + lodash.map: 4.6.0 + longest: 2.0.1 + word-wrap: 1.2.5 + optionalDependencies: + '@commitlint/load': 20.0.0(@types/node@24.8.1)(typescript@5.6.3) + transitivePeerDependencies: + - '@types/node' + - typescript + + cz-git@1.12.0: {} + + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + + danmu.js@1.1.13: + dependencies: + event-emitter: 0.3.5 + + dargs@8.1.0: {} + + dayjs@1.11.18: {} + + de-indent@1.0.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dedent@0.7.0: {} + + deep-is@0.1.4: {} + + deep-pick-omit@1.2.1: {} + + default-browser-id@5.0.0: {} + + default-browser@5.2.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-lazy-prop@2.0.0: {} + + define-lazy-prop@3.0.0: {} + + defu@6.1.4: {} + + delayed-stream@1.0.0: {} + + delegate@3.2.0: {} + + destr@2.0.5: {} + + detect-file@1.0.0: {} + + detect-indent@6.1.0: {} + + detect-libc@1.0.3: + optional: true + + detect-libc@2.1.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + dom7@3.0.0: + dependencies: + ssr-window: 3.0.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + downloadjs@1.4.7: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + echarts@6.0.0: + dependencies: + tslib: 2.3.0 + zrender: 6.0.0 + + electron-to-chromium@1.5.227: {} + + element-plus@2.11.4(vue@3.5.22(typescript@5.6.3)): + dependencies: + '@ctrl/tinycolor': 3.6.1 + '@element-plus/icons-vue': 2.3.2(vue@3.5.22(typescript@5.6.3)) + '@floating-ui/dom': 1.7.4 + '@popperjs/core': '@sxzz/popperjs-es@2.11.7' + '@types/lodash': 4.17.20 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 9.13.0(vue@3.5.22(typescript@5.6.3)) + async-validator: 4.2.5 + dayjs: 1.11.18 + escape-html: 1.0.3 + lodash: 4.17.21 + lodash-es: 4.17.21 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.22(typescript@5.6.3) + transitivePeerDependencies: + - '@vue/composition-api' + + emoji-regex@10.5.0: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser-es@0.1.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-config-prettier@9.1.2(eslint@9.36.0(jiti@2.6.0)): + dependencies: + eslint: 9.36.0(jiti@2.6.0) + + eslint-plugin-prettier@5.5.4(eslint-config-prettier@9.1.2(eslint@9.36.0(jiti@2.6.0)))(eslint@9.36.0(jiti@2.6.0))(prettier@3.6.2): + dependencies: + eslint: 9.36.0(jiti@2.6.0) + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 9.1.2(eslint@9.36.0(jiti@2.6.0)) + + eslint-plugin-vue@9.33.0(eslint@9.36.0(jiti@2.6.0)): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + eslint: 9.36.0(jiti@2.6.0) + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.2 + vue-eslint-parser: 9.4.3(eslint@9.36.0(jiti@2.6.0)) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.36.0(jiti@2.6.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.36.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.0 + transitivePeerDependencies: + - supports-color + + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.1: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + execa@9.6.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + + exsolve@1.0.7: {} + + ext@1.7.0: + dependencies: + type: 2.7.3 + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-uri@3.1.0: {} + + fastest-levenshtein@1.0.16: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + file-entry-cache@10.1.4: + dependencies: + flat-cache: 6.1.14 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + file-saver@2.0.5: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-node-modules@2.1.3: + dependencies: + findup-sync: 4.0.0 + merge: 2.1.1 + + find-root@1.1.0: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-up@7.0.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + + findup-sync@4.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flat-cache@6.1.14: + dependencies: + cacheable: 2.0.2 + flatted: 3.3.3 + hookified: 1.12.1 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + frac@1.1.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.4.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@8.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + git-raw-commits@4.0.0: + dependencies: + dargs: 8.1.0 + meow: 12.1.1 + split2: 4.2.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globals@14.0.0: {} + + globals@15.15.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + globjoin@0.1.4: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + highlight.js@11.11.1: {} + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + hookable@5.5.3: {} + + hookified@1.12.1: {} + + html-tags@3.3.1: {} + + html-void-elements@2.0.1: {} + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + + human-signals@5.0.0: {} + + human-signals@8.0.1: {} + + husky@9.1.7: {} + + i18next@20.6.1: + dependencies: + '@babel/runtime': 7.28.4 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + immer@9.0.21: {} + + immutable@5.1.3: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.2.0: {} + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ini@4.1.1: {} + + inquirer@8.2.5: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-docker@2.2.1: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hotkey@0.2.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@1.0.0: {} + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-plain-object@5.0.0: {} + + is-stream@3.0.0: {} + + is-stream@4.0.1: {} + + is-text-path@2.0.0: + dependencies: + text-extensions: 2.4.0 + + is-unicode-supported@0.1.0: {} + + is-unicode-supported@2.1.0: {} + + is-url@1.2.4: {} + + is-utf8@0.2.1: {} + + is-what@4.1.16: {} + + is-windows@1.0.2: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + jiti@2.6.0: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonparse@1.3.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + keyv@5.5.3: + dependencies: + '@keyv/serialize': 1.1.1 + + kind-of@6.0.3: {} + + known-css-properties@0.36.0: {} + + known-css-properties@0.37.0: {} + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lint-staged@15.5.2: + dependencies: + chalk: 5.6.2 + commander: 13.1.0 + debug: 4.4.3 + execa: 8.0.1 + lilconfig: 3.1.3 + listr2: 8.3.3 + micromatch: 4.0.8 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.1 + transitivePeerDependencies: + - supports-color + + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash-es@4.17.21: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.17.21 + lodash-es: 4.17.21 + + lodash.camelcase@4.3.0: {} + + lodash.clonedeep@4.5.0: {} + + lodash.debounce@4.0.8: {} + + lodash.foreach@4.5.0: {} + + lodash.isequal@4.5.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.kebabcase@4.1.1: {} + + lodash.map@4.6.0: {} + + lodash.merge@4.6.2: {} + + lodash.mergewith@4.6.2: {} + + lodash.snakecase@4.1.1: {} + + lodash.startcase@4.4.0: {} + + lodash.throttle@4.1.1: {} + + lodash.toarray@4.4.0: {} + + lodash.truncate@4.4.2: {} + + lodash.uniq@4.5.0: {} + + lodash.upperfirst@4.3.1: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.1.1 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + longest@2.0.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + mathml-tag-names@2.1.3: {} + + mdn-data@2.12.2: {} + + mdn-data@2.24.0: {} + + memoize-one@6.0.0: {} + + meow@12.1.1: {} + + meow@13.2.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + merge@2.1.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-match@1.0.2: + dependencies: + wildcard: 1.1.2 + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.7: {} + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + + mitt@3.0.1: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + mute-stream@0.0.8: {} + + namespace-emitter@2.0.1: {} + + nanoid@3.3.11: {} + + nanoid@5.1.6: {} + + natural-compare@1.4.0: {} + + next-tick@1.1.0: {} + + node-addon-api@7.1.1: + optional: true + + node-releases@2.0.21: {} + + normalize-path@3.0.0: {} + + normalize-wheel-es@1.2.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nprogress@0.2.0: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + ohash@2.0.11: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@10.2.0: + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + os-tmpdir@1.0.2: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.1 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-ms@4.0.0: {} + + parse-passwd@1.0.0: {} + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-exists@5.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pinia-plugin-persistedstate@4.5.0(pinia@3.0.3(typescript@5.6.3)(vue@3.5.22(typescript@5.6.3))): + dependencies: + deep-pick-omit: 1.2.1 + defu: 6.1.4 + destr: 2.0.5 + optionalDependencies: + pinia: 3.0.3(typescript@5.6.3)(vue@3.5.22(typescript@5.6.3)) + + pinia@3.0.3(typescript@5.6.3)(vue@3.5.22(typescript@5.6.3)): + dependencies: + '@vue/devtools-api': 7.7.7 + vue: 3.5.22(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + postcss-html@1.8.0: + dependencies: + htmlparser2: 8.0.2 + js-tokens: 9.0.1 + postcss: 8.5.6 + postcss-safe-parser: 6.0.0(postcss@8.5.6) + + postcss-media-query-parser@0.2.3: {} + + postcss-resolve-nested-selector@0.1.6: {} + + postcss-safe-parser@6.0.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-sorting@8.0.2(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.27.2: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.2: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + prismjs@1.30.0: {} + + proxy-from-env@1.1.0: {} + + punycode@2.3.1: {} + + qrcode.vue@3.6.0(vue@3.5.22(typescript@5.6.3)): + dependencies: + vue: 3.5.22(typescript@5.6.3) + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup-plugin-visualizer@5.14.0(rollup@4.52.3): + dependencies: + open: 8.4.2 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.52.3 + + rollup@4.52.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.3 + '@rollup/rollup-android-arm64': 4.52.3 + '@rollup/rollup-darwin-arm64': 4.52.3 + '@rollup/rollup-darwin-x64': 4.52.3 + '@rollup/rollup-freebsd-arm64': 4.52.3 + '@rollup/rollup-freebsd-x64': 4.52.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.3 + '@rollup/rollup-linux-arm-musleabihf': 4.52.3 + '@rollup/rollup-linux-arm64-gnu': 4.52.3 + '@rollup/rollup-linux-arm64-musl': 4.52.3 + '@rollup/rollup-linux-loong64-gnu': 4.52.3 + '@rollup/rollup-linux-ppc64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-gnu': 4.52.3 + '@rollup/rollup-linux-riscv64-musl': 4.52.3 + '@rollup/rollup-linux-s390x-gnu': 4.52.3 + '@rollup/rollup-linux-x64-gnu': 4.52.3 + '@rollup/rollup-linux-x64-musl': 4.52.3 + '@rollup/rollup-openharmony-arm64': 4.52.3 + '@rollup/rollup-win32-arm64-msvc': 4.52.3 + '@rollup/rollup-win32-ia32-msvc': 4.52.3 + '@rollup/rollup-win32-x64-gnu': 4.52.3 + '@rollup/rollup-win32-x64-msvc': 4.52.3 + fsevents: 2.3.3 + + run-applescript@7.1.0: {} + + run-async@2.4.1: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sass@1.93.2: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.3 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + scule@1.3.0: {} + + semver@6.3.1: {} + + semver@7.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + slash@3.0.0: {} + + slate-history@0.66.0(slate@0.72.8): + dependencies: + is-plain-object: 5.0.0 + slate: 0.72.8 + + slate@0.72.8: + dependencies: + immer: 9.0.21 + is-plain-object: 5.0.0 + tiny-warning: 1.0.3 + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + snabbdom@3.6.2: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + speakingurl@14.0.1: {} + + split2@4.2.0: {} + + ssf@0.11.2: + dependencies: + frac: 1.1.2 + + ssr-window@3.0.0: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.5.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@4.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-final-newline@4.0.0: {} + + strip-json-comments@3.1.1: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + stylelint-config-html@1.1.0(postcss-html@1.8.0)(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + postcss-html: 1.8.0 + stylelint: 16.24.0(typescript@5.6.3) + + stylelint-config-recess-order@4.6.0(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + stylelint: 16.24.0(typescript@5.6.3) + stylelint-order: 6.0.4(stylelint@16.24.0(typescript@5.6.3)) + + stylelint-config-recommended-scss@14.1.0(postcss@8.5.6)(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + postcss-scss: 4.0.9(postcss@8.5.6) + stylelint: 16.24.0(typescript@5.6.3) + stylelint-config-recommended: 14.0.1(stylelint@16.24.0(typescript@5.6.3)) + stylelint-scss: 6.12.1(stylelint@16.24.0(typescript@5.6.3)) + optionalDependencies: + postcss: 8.5.6 + + stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.0)(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + postcss-html: 1.8.0 + semver: 7.7.2 + stylelint: 16.24.0(typescript@5.6.3) + stylelint-config-html: 1.1.0(postcss-html@1.8.0)(stylelint@16.24.0(typescript@5.6.3)) + stylelint-config-recommended: 17.0.0(stylelint@16.24.0(typescript@5.6.3)) + + stylelint-config-recommended@14.0.1(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + stylelint: 16.24.0(typescript@5.6.3) + + stylelint-config-recommended@17.0.0(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + stylelint: 16.24.0(typescript@5.6.3) + + stylelint-config-standard@36.0.1(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + stylelint: 16.24.0(typescript@5.6.3) + stylelint-config-recommended: 14.0.1(stylelint@16.24.0(typescript@5.6.3)) + + stylelint-order@6.0.4(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + postcss: 8.5.6 + postcss-sorting: 8.0.2(postcss@8.5.6) + stylelint: 16.24.0(typescript@5.6.3) + + stylelint-scss@6.12.1(stylelint@16.24.0(typescript@5.6.3)): + dependencies: + css-tree: 3.1.0 + is-plain-object: 5.0.0 + known-css-properties: 0.36.0 + mdn-data: 2.24.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-selector-parser: 7.1.0 + postcss-value-parser: 4.2.0 + stylelint: 16.24.0(typescript@5.6.3) + + stylelint@16.24.0(typescript@5.6.3): + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@dual-bundle/import-meta-resolve': 4.2.1 + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.6.3) + css-functions-list: 3.2.3 + css-tree: 3.1.0 + debug: 4.4.3 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 10.1.4 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.3.1 + ignore: 7.0.5 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mathml-tag-names: 2.1.3 + meow: 13.2.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-resolve-nested-selector: 0.1.6 + postcss-safe-parser: 7.0.1(postcss@8.5.6) + postcss-selector-parser: 7.1.0 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + supports-hyperlinks: 3.2.0 + svg-tags: 1.0.0 + table: 6.9.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + - typescript + + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + svg-tags@1.0.0: {} + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + table@6.9.0: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tailwindcss@4.1.14: {} + + tapable@2.3.0: {} + + tar@7.5.1: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + + terser@5.44.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + text-extensions@2.4.0: {} + + through@2.3.8: {} + + tiny-warning@1.0.3: {} + + tinyexec@1.0.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + totalist@3.0.1: {} + + ts-api-utils@2.1.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + + tslib@2.3.0: {} + + tslib@2.8.1: {} + + tsx@4.20.6: + dependencies: + esbuild: 0.25.10 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type@2.7.3: {} + + typescript-eslint@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + '@typescript-eslint/parser': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.6.3) + '@typescript-eslint/utils': 8.44.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.6.3) + eslint: 9.36.0(jiti@2.6.0) + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + typescript@5.6.3: {} + + ufo@1.6.1: {} + + undici-types@7.14.0: {} + + unicorn-magic@0.1.0: {} + + unicorn-magic@0.3.0: {} + + unimport@5.4.0: + dependencies: + acorn: 8.15.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.1.2 + magic-string: 0.30.19 + mlly: 1.8.0 + pathe: 2.0.3 + picomatch: 4.0.3 + pkg-types: 2.3.0 + scule: 1.3.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.15 + unplugin: 2.3.10 + unplugin-utils: 0.3.0 + + universalify@2.0.1: {} + + unplugin-auto-import@20.2.0(@vueuse/core@13.9.0(vue@3.5.22(typescript@5.6.3))): + dependencies: + local-pkg: 1.1.2 + magic-string: 0.30.19 + picomatch: 4.0.3 + unimport: 5.4.0 + unplugin: 2.3.10 + unplugin-utils: 0.3.0 + optionalDependencies: + '@vueuse/core': 13.9.0(vue@3.5.22(typescript@5.6.3)) + + unplugin-element-plus@0.10.0: + dependencies: + es-module-lexer: 1.7.0 + magic-string: 0.30.19 + unplugin: 2.3.10 + unplugin-utils: 0.2.5 + + unplugin-utils@0.2.5: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + unplugin-utils@0.3.0: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.3 + + unplugin-vue-components@29.1.0(@babel/parser@7.28.4)(vue@3.5.22(typescript@5.6.3)): + dependencies: + chokidar: 3.6.0 + debug: 4.4.3 + local-pkg: 1.1.2 + magic-string: 0.30.19 + mlly: 1.8.0 + tinyglobby: 0.2.15 + unplugin: 2.3.10 + unplugin-utils: 0.3.0 + vue: 3.5.22(typescript@5.6.3) + optionalDependencies: + '@babel/parser': 7.28.4 + transitivePeerDependencies: + - supports-color + + unplugin@2.3.10: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.3(browserslist@4.26.2): + dependencies: + browserslist: 4.26.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite-hot-client@2.1.0(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): + dependencies: + vite: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + + vite-plugin-compression@0.5.1(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + vite: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + + vite-plugin-inspect@0.8.9(rollup@4.52.3)(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): + dependencies: + '@antfu/utils': 0.7.10 + '@rollup/pluginutils': 5.3.0(rollup@4.52.3) + debug: 4.4.3 + error-stack-parser-es: 0.1.5 + fs-extra: 11.3.2 + open: 10.2.0 + perfect-debounce: 1.0.0 + picocolors: 1.1.1 + sirv: 3.0.2 + vite: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - rollup + - supports-color + + vite-plugin-vue-devtools@7.7.7(rollup@4.52.3)(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.6.3)): + dependencies: + '@vue/devtools-core': 7.7.7(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.6.3)) + '@vue/devtools-kit': 7.7.7 + '@vue/devtools-shared': 7.7.7 + execa: 9.6.0 + sirv: 3.0.2 + vite: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vite-plugin-inspect: 0.8.9(rollup@4.52.3)(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + vite-plugin-vue-inspector: 5.3.2(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + transitivePeerDependencies: + - '@nuxt/kit' + - rollup + - supports-color + - vue + + vite-plugin-vue-inspector@5.3.2(vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)): + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.4) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.4) + '@vue/compiler-dom': 3.5.22 + kolorist: 1.8.0 + magic-string: 0.30.19 + vite: 7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + + vite@7.1.7(@types/node@24.8.1)(jiti@2.6.0)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): + dependencies: + esbuild: 0.25.10 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.8.1 + fsevents: 2.3.3 + jiti: 2.6.0 + lightningcss: 1.30.1 + sass: 1.93.2 + terser: 5.44.0 + tsx: 4.20.6 + yaml: 2.8.1 + + vscode-uri@3.1.0: {} + + vue-demi@0.14.10(vue@3.5.22(typescript@5.6.3)): + dependencies: + vue: 3.5.22(typescript@5.6.3) + + vue-draggable-plus@0.6.0(@types/sortablejs@1.15.8): + dependencies: + '@types/sortablejs': 1.15.8 + + vue-eslint-parser@9.4.3(eslint@9.36.0(jiti@2.6.0)): + dependencies: + debug: 4.4.3 + eslint: 9.36.0(jiti@2.6.0) + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + vue-i18n@9.14.5(vue@3.5.22(typescript@5.6.3)): + dependencies: + '@intlify/core-base': 9.14.5 + '@intlify/shared': 9.14.5 + '@vue/devtools-api': 6.6.4 + vue: 3.5.22(typescript@5.6.3) + + vue-img-cutter@3.0.7(typescript@5.6.3): + dependencies: + core-js: 3.45.1 + vue: 3.5.22(typescript@5.6.3) + vue-i18n: 9.14.5(vue@3.5.22(typescript@5.6.3)) + transitivePeerDependencies: + - typescript + + vue-router@4.5.1(vue@3.5.22(typescript@5.6.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.22(typescript@5.6.3) + + vue-tsc@2.1.10(typescript@5.6.3): + dependencies: + '@volar/typescript': 2.4.23 + '@vue/language-core': 2.1.10(typescript@5.6.3) + semver: 7.7.2 + typescript: 5.6.3 + + vue@3.5.22(typescript@5.6.3): + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-sfc': 3.5.22 + '@vue/runtime-dom': 3.5.22 + '@vue/server-renderer': 3.5.22(vue@3.5.22(typescript@5.6.3)) + '@vue/shared': 3.5.22 + optionalDependencies: + typescript: 5.6.3 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webpack-virtual-modules@0.6.2: {} + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wildcard@1.1.2: {} + + wmf@1.0.2: {} + + word-wrap@1.2.5: {} + + word@0.3.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + + xgplayer-subtitles@3.0.23(core-js@3.45.1): + dependencies: + core-js: 3.45.1 + eventemitter3: 4.0.7 + + xgplayer@3.0.23(core-js@3.45.1): + dependencies: + core-js: 3.45.1 + danmu.js: 1.1.13 + delegate: 3.2.0 + downloadjs: 1.4.7 + eventemitter3: 4.0.7 + xgplayer-subtitles: 3.0.23(core-js@3.45.1) + + xlsx@0.18.5: + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + + xml-name-validator@4.0.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + yaml@2.8.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.1: {} + + yoctocolors@2.1.2: {} + + zrender@6.0.0: + dependencies: + tslib: 2.3.0 diff --git a/adminSystem/public/favicon.ico b/adminSystem/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/adminSystem/scripts/clean-dev.ts b/adminSystem/scripts/clean-dev.ts new file mode 100644 index 0000000..cc0b9bc --- /dev/null +++ b/adminSystem/scripts/clean-dev.ts @@ -0,0 +1,838 @@ +// scripts/clean-dev.ts +import fs from 'fs/promises' +import path from 'path' + +// 现代化颜色主题 +const theme = { + // 基础颜色 + reset: '\x1b[0m', + bold: '\x1b[1m', + dim: '\x1b[2m', + + // 前景色 + primary: '\x1b[38;5;75m', // 亮蓝色 + success: '\x1b[38;5;82m', // 亮绿色 + warning: '\x1b[38;5;220m', // 亮黄色 + error: '\x1b[38;5;196m', // 亮红色 + info: '\x1b[38;5;159m', // 青色 + purple: '\x1b[38;5;141m', // 紫色 + orange: '\x1b[38;5;208m', // 橙色 + gray: '\x1b[38;5;245m', // 灰色 + white: '\x1b[38;5;255m', // 白色 + + // 背景色 + bgDark: '\x1b[48;5;235m', // 深灰背景 + bgBlue: '\x1b[48;5;24m', // 蓝色背景 + bgGreen: '\x1b[48;5;22m', // 绿色背景 + bgRed: '\x1b[48;5;52m' // 红色背景 +} + +// 现代化图标集 +const icons = { + rocket: '🚀', + fire: '🔥', + star: '⭐', + gem: '💎', + crown: '👑', + magic: '✨', + warning: '⚠️', + success: '✅', + error: '❌', + info: 'ℹ️', + folder: '📁', + file: '📄', + image: '🖼️', + code: '💻', + data: '📊', + globe: '🌐', + map: '🗺️', + chat: '💬', + bolt: '⚡', + shield: '🛡️', + key: '🔑', + link: '🔗', + clean: '🧹', + trash: '🗑️', + check: '✓', + cross: '✗', + arrow: '→', + loading: '⏳' +} + +// 格式化工具 +const fmt = { + title: (text: string) => `${theme.bold}${theme.primary}${text}${theme.reset}`, + subtitle: (text: string) => `${theme.purple}${text}${theme.reset}`, + success: (text: string) => `${theme.success}${text}${theme.reset}`, + error: (text: string) => `${theme.error}${text}${theme.reset}`, + warning: (text: string) => `${theme.warning}${text}${theme.reset}`, + info: (text: string) => `${theme.info}${text}${theme.reset}`, + highlight: (text: string) => `${theme.bold}${theme.white}${text}${theme.reset}`, + dim: (text: string) => `${theme.dim}${theme.gray}${text}${theme.reset}`, + orange: (text: string) => `${theme.orange}${text}${theme.reset}`, + + // 带背景的文本 + badge: (text: string, bg: string = theme.bgBlue) => + `${bg}${theme.white}${theme.bold} ${text} ${theme.reset}`, + + // 渐变效果模拟 + gradient: (text: string) => { + const colors = ['\x1b[38;5;75m', '\x1b[38;5;81m', '\x1b[38;5;87m', '\x1b[38;5;159m'] + const chars = text.split('') + return chars.map((char, i) => `${colors[i % colors.length]}${char}`).join('') + theme.reset + } +} + +// 创建现代化标题横幅 +function createModernBanner() { + console.log() + console.log( + fmt.gradient(' ╔══════════════════════════════════════════════════════════════════╗') + ) + console.log( + fmt.gradient(' ║ ║') + ) + console.log( + ` ║ ${icons.rocket} ${fmt.title('ART DESIGN PRO')} ${fmt.subtitle('· 代码精简程序')} ${icons.magic} ║` + ) + console.log( + ` ║ ${fmt.dim('为项目移除演示数据,快速切换至开发模式')} ║` + ) + console.log( + fmt.gradient(' ║ ║') + ) + console.log( + fmt.gradient(' ╚══════════════════════════════════════════════════════════════════╝') + ) + console.log() +} + +// 创建分割线 +function createDivider(char = '─', color = theme.primary) { + console.log(`${color}${' ' + char.repeat(66)}${theme.reset}`) +} + +// 创建卡片样式容器 +function createCard(title: string, content: string[]) { + console.log(` ${fmt.badge('', theme.bgBlue)} ${fmt.title(title)}`) + console.log() + content.forEach((line) => { + console.log(` ${line}`) + }) + console.log() +} + +// 进度条动画 +function createProgressBar(current: number, total: number, text: string, width = 40) { + const percentage = Math.round((current / total) * 100) + const filled = Math.round((current / total) * width) + const empty = width - filled + + const filledBar = '█'.repeat(filled) + const emptyBar = '░'.repeat(empty) + + process.stdout.write( + `\r ${fmt.info('进度')} [${theme.success}${filledBar}${theme.gray}${emptyBar}${theme.reset}] ${fmt.highlight(percentage + '%')})}` + ) + + if (current === total) { + console.log() + } +} + +// 统计信息 +const stats = { + deletedFiles: 0, + deletedPaths: 0, + failedPaths: 0, + startTime: Date.now(), + totalFiles: 0 +} + +// 清理目标 +const targets = [ + 'README.md', + 'README.zh-CN.md', + 'CHANGELOG.md', + 'CHANGELOG.zh-CN.md', + 'src/views/change', + 'src/views/safeguard', + 'src/views/article', + 'src/views/examples', + 'src/views/system/nested', + 'src/views/widgets', + 'src/views/template', + 'src/views/dashboard/analysis', + 'src/views/dashboard/ecommerce', + 'src/mock/json', + 'src/mock/temp/articleList.ts', + 'src/mock/temp/commentDetail.ts', + 'src/mock/temp/commentList.ts', + 'src/assets/images/cover', + 'src/assets/images/safeguard', + 'src/assets/images/3d', + 'src/components/core/charts/art-map-chart', + 'src/components/business/comment-widget' +] + +// 递归统计文件数量 +async function countFiles(targetPath: string): Promise { + const fullPath = path.resolve(process.cwd(), targetPath) + + try { + const stat = await fs.stat(fullPath) + + if (stat.isFile()) { + return 1 + } else if (stat.isDirectory()) { + const entries = await fs.readdir(fullPath) + let count = 0 + + for (const entry of entries) { + const entryPath = path.join(targetPath, entry) + count += await countFiles(entryPath) + } + + return count + } + } catch { + return 0 + } + + return 0 +} + +// 统计所有目标的文件数量 +async function countAllFiles(): Promise { + let totalCount = 0 + + for (const target of targets) { + const count = await countFiles(target) + totalCount += count + } + + return totalCount +} + +// 删除文件和目录 +async function remove(targetPath: string, index: number) { + const fullPath = path.resolve(process.cwd(), targetPath) + + createProgressBar(index + 1, targets.length, targetPath) + + try { + const fileCount = await countFiles(targetPath) + await fs.rm(fullPath, { recursive: true, force: true }) + stats.deletedFiles += fileCount + stats.deletedPaths++ + await new Promise((resolve) => setTimeout(resolve, 50)) + } catch (err) { + stats.failedPaths++ + console.log() + console.log(` ${icons.error} ${fmt.error('删除失败')}: ${fmt.highlight(targetPath)}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + } +} + +// 清理路由模块 +async function cleanRouteModules() { + const modulesPath = path.resolve(process.cwd(), 'src/router/modules') + + try { + // 删除演示相关的路由模块 + const modulesToRemove = [ + 'template.ts', + 'widgets.ts', + 'examples.ts', + 'article.ts', + 'safeguard.ts', + 'help.ts' + ] + + for (const module of modulesToRemove) { + const modulePath = path.join(modulesPath, module) + try { + await fs.rm(modulePath, { force: true }) + } catch { + // 文件不存在时忽略错误 + } + } + + // 重写 dashboard.ts - 只保留 console + const dashboardContent = `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 + } + } + ] +} +` + await fs.writeFile(path.join(modulesPath, 'dashboard.ts'), dashboardContent, 'utf-8') + + // 重写 system.ts - 移除 nested 嵌套菜单 + const systemContent = `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' } + ] + } + } + ] +} +` + await fs.writeFile(path.join(modulesPath, 'system.ts'), systemContent, 'utf-8') + + // 重写 index.ts - 只导入保留的模块 + const indexContent = `import { AppRouteRecord } from '@/types/router' +import { dashboardRoutes } from './dashboard' +import { systemRoutes } from './system' +import { resultRoutes } from './result' +import { exceptionRoutes } from './exception' + +/** + * 导出所有模块化路由 + */ +export const routeModules: AppRouteRecord[] = [ + dashboardRoutes, + systemRoutes, + resultRoutes, + exceptionRoutes +] +` + await fs.writeFile(path.join(modulesPath, 'index.ts'), indexContent, 'utf-8') + + console.log(` ${icons.success} ${fmt.success('清理路由模块完成')}`) + } catch (err) { + console.log(` ${icons.error} ${fmt.error('清理路由模块失败')}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + } +} + +// 清理路由别名 +async function cleanRoutesAlias() { + const routesAliasPath = path.resolve(process.cwd(), 'src/router/routesAlias.ts') + + try { + const cleanedAlias = `/** + * 公共路由别名 + # 存放系统级公共路由路径,如布局容器、登录页等 + */ +export enum RoutesAlias { + Layout = '/index/index', // 布局容器 + Login = '/auth/login' // 登录页 +} +` + + await fs.writeFile(routesAliasPath, cleanedAlias, 'utf-8') + console.log(` ${icons.success} ${fmt.success('重写路由别名配置完成')}`) + } catch (err) { + console.log(` ${icons.error} ${fmt.error('清理路由别名失败')}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + } +} + +// 清理变更日志 +async function cleanChangeLog() { + const changeLogPath = path.resolve(process.cwd(), 'src/mock/upgrade/changeLog.ts') + + try { + const cleanedChangeLog = `import { ref } from 'vue' + +interface UpgradeLog { + version: string // 版本号 + title: string // 更新标题 + date: string // 更新日期 + detail?: string[] // 更新内容 + requireReLogin?: boolean // 是否需要重新登录 + remark?: string // 备注 +} + +export const upgradeLogList = ref([]) +` + + await fs.writeFile(changeLogPath, cleanedChangeLog, 'utf-8') + console.log(` ${icons.success} ${fmt.success('清空变更日志数据完成')}`) + } catch (err) { + console.log(` ${icons.error} ${fmt.error('清理变更日志失败')}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + } +} + +// 清理语言文件 +async function cleanLanguageFiles() { + const languageFiles = [ + { path: 'src/locales/langs/zh.json', name: '中文语言文件' }, + { path: 'src/locales/langs/en.json', name: '英文语言文件' } + ] + + for (const { path: langPath, name } of languageFiles) { + try { + const fullPath = path.resolve(process.cwd(), langPath) + const content = await fs.readFile(fullPath, 'utf-8') + const langData = JSON.parse(content) + + const menusToRemove = [ + 'widgets', + 'template', + 'article', + 'examples', + 'safeguard', + 'plan', + 'help' + ] + + if (langData.menus) { + menusToRemove.forEach((menuKey) => { + if (langData.menus[menuKey]) { + delete langData.menus[menuKey] + } + }) + + if (langData.menus.dashboard) { + if (langData.menus.dashboard.analysis) { + delete langData.menus.dashboard.analysis + } + if (langData.menus.dashboard.ecommerce) { + delete langData.menus.dashboard.ecommerce + } + } + + if (langData.menus.system) { + const systemKeysToRemove = [ + 'nested', + 'menu1', + 'menu2', + 'menu21', + 'menu3', + 'menu31', + 'menu32', + 'menu321' + ] + systemKeysToRemove.forEach((key) => { + if (langData.menus.system[key]) { + delete langData.menus.system[key] + } + }) + } + } + + await fs.writeFile(fullPath, JSON.stringify(langData, null, 2), 'utf-8') + console.log(` ${icons.success} ${fmt.success(`清理${name}完成`)}`) + } catch (err) { + console.log(` ${icons.error} ${fmt.error(`清理${name}失败`)}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + } + } +} + +// 清理快速入口组件 +async function cleanFastEnterComponent() { + const fastEnterPath = path.resolve(process.cwd(), 'src/config/fastEnter.ts') + + try { + const cleanedFastEnter = `/** + * 快速入口配置 + * 包含:应用列表、快速链接等配置 + */ +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' + } + ] +} + +export default Object.freeze(fastEnterConfig) +` + + await fs.writeFile(fastEnterPath, cleanedFastEnter, 'utf-8') + console.log(` ${icons.success} ${fmt.success('清理快速入口配置完成')}`) + } catch (err) { + console.log(` ${icons.error} ${fmt.error('清理快速入口配置失败')}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + } +} + +// 更新菜单接口 +async function updateMenuApi() { + const apiPath = path.resolve(process.cwd(), 'src/api/system-manage.ts') + + try { + const content = await fs.readFile(apiPath, 'utf-8') + const updatedContent = content.replace( + "url: '/api/v3/system/menus'", + "url: '/api/v3/system/menus/simple'" + ) + + await fs.writeFile(apiPath, updatedContent, 'utf-8') + console.log(` ${icons.success} ${fmt.success('更新菜单接口完成')}`) + } catch (err) { + console.log(` ${icons.error} ${fmt.error('更新菜单接口失败')}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + } +} + +// 用户确认函数 +async function getUserConfirmation(): Promise { + const { createInterface } = await import('readline') + + return new Promise((resolve) => { + const rl = createInterface({ + input: process.stdin, + output: process.stdout + }) + + console.log( + ` ${fmt.highlight('请输入')} ${fmt.success('yes')} ${fmt.highlight('确认执行清理操作,或按 Enter 取消')}` + ) + console.log() + process.stdout.write(` ${icons.arrow} `) + + rl.question('', (answer: string) => { + rl.close() + resolve(answer.toLowerCase().trim() === 'yes') + }) + }) +} + +// 显示清理警告 +async function showCleanupWarning() { + createCard('安全警告', [ + `${fmt.warning('此操作将永久删除以下演示内容,且无法恢复!')}`, + `${fmt.dim('请仔细阅读清理列表,确认后再继续操作')}` + ]) + + const cleanupItems = [ + { + icon: icons.image, + name: '图片资源', + desc: '演示用的封面图片、3D图片、运维图片等', + color: theme.orange + }, + { + icon: icons.file, + name: '演示页面', + desc: 'widgets、template、article、examples、safeguard等页面', + color: theme.purple + }, + { + icon: icons.code, + name: '路由模块文件', + desc: '删除演示路由模块,只保留核心模块(dashboard、system、result、exception)', + color: theme.primary + }, + { + icon: icons.link, + name: '路由别名', + desc: '重写routesAlias.ts,移除演示路由别名', + color: theme.info + }, + { + icon: icons.data, + name: 'Mock数据', + desc: '演示用的JSON数据、文章列表、评论数据等', + color: theme.success + }, + { + icon: icons.globe, + name: '多语言文件', + desc: '清理中英文语言包中的演示菜单项', + color: theme.warning + }, + { icon: icons.map, name: '地图组件', desc: '移除art-map-chart地图组件', color: theme.error }, + { icon: icons.chat, name: '评论组件', desc: '移除comment-widget评论组件', color: theme.orange }, + { + icon: icons.bolt, + name: '快速入口', + desc: '移除分析页、礼花效果、聊天、更新日志、定价、留言管理等无效项目', + color: theme.purple + } + ] + + console.log(` ${fmt.badge('', theme.bgRed)} ${fmt.title('将要清理的内容')}`) + console.log() + + cleanupItems.forEach((item, index) => { + console.log(` ${item.color}${theme.reset} ${fmt.highlight(`${index + 1}. ${item.name}`)}`) + console.log(` ${fmt.dim(item.desc)}`) + }) + + console.log() + console.log(` ${fmt.badge('', theme.bgGreen)} ${fmt.title('保留的功能模块')}`) + console.log() + + const preservedModules = [ + { name: 'Dashboard', desc: '工作台页面' }, + { name: 'System', desc: '系统管理模块' }, + { name: 'Result', desc: '结果页面' }, + { name: 'Exception', desc: '异常页面' }, + { name: 'Auth', desc: '登录注册功能' }, + { name: 'Core Components', desc: '核心组件库' } + ] + + preservedModules.forEach((module) => { + console.log(` ${icons.check} ${fmt.success(module.name)} ${fmt.dim(`- ${module.desc}`)}`) + }) + + console.log() + createDivider() + console.log() +} + +// 显示统计信息 +async function showStats() { + const duration = Date.now() - stats.startTime + const seconds = (duration / 1000).toFixed(2) + + console.log() + createCard('清理统计', [ + `${fmt.success('成功删除')}: ${fmt.highlight(stats.deletedFiles.toString())} 个文件`, + `${fmt.info('涉及路径')}: ${fmt.highlight(stats.deletedPaths.toString())} 个目录/文件`, + ...(stats.failedPaths > 0 + ? [ + `${icons.error} ${fmt.error('删除失败')}: ${fmt.highlight(stats.failedPaths.toString())} 个路径` + ] + : []), + `${fmt.info('耗时')}: ${fmt.highlight(seconds)} 秒` + ]) +} + +// 创建成功横幅 +function createSuccessBanner() { + console.log() + console.log( + fmt.gradient(' ╔══════════════════════════════════════════════════════════════════╗') + ) + console.log( + fmt.gradient(' ║ ║') + ) + console.log( + ` ║ ${icons.star} ${fmt.success('清理完成!项目已准备就绪')} ${icons.rocket} ║` + ) + console.log( + ` ║ ${fmt.dim('现在可以开始您的开发之旅了!')} ║` + ) + console.log( + fmt.gradient(' ║ ║') + ) + console.log( + fmt.gradient(' ╚══════════════════════════════════════════════════════════════════╝') + ) + console.log() +} + +// 主函数 +async function main() { + // 清屏并显示横幅 + console.clear() + createModernBanner() + + // 显示清理警告 + await showCleanupWarning() + + // 统计文件数量 + console.log(` ${fmt.info('正在统计文件数量...')}`) + stats.totalFiles = await countAllFiles() + + console.log(` ${fmt.info('即将清理')}: ${fmt.highlight(stats.totalFiles.toString())} 个文件`) + console.log(` ${fmt.dim(`涉及 ${targets.length} 个目录/文件路径`)}`) + console.log() + + // 用户确认 + const confirmed = await getUserConfirmation() + + if (!confirmed) { + console.log(` ${fmt.warning('操作已取消,清理中止')}`) + console.log() + return + } + + console.log() + console.log(` ${icons.check} ${fmt.success('确认成功,开始清理...')}`) + console.log() + + // 开始清理过程 + console.log(` ${fmt.badge('步骤 1/6', theme.bgBlue)} ${fmt.title('删除演示文件')}`) + console.log() + for (let i = 0; i < targets.length; i++) { + await remove(targets[i], i) + } + console.log() + + console.log(` ${fmt.badge('步骤 2/6', theme.bgBlue)} ${fmt.title('清理路由模块')}`) + console.log() + await cleanRouteModules() + console.log() + + console.log(` ${fmt.badge('步骤 3/6', theme.bgBlue)} ${fmt.title('重写路由别名')}`) + console.log() + await cleanRoutesAlias() + console.log() + + console.log(` ${fmt.badge('步骤 4/6', theme.bgBlue)} ${fmt.title('清空变更日志')}`) + console.log() + await cleanChangeLog() + console.log() + + console.log(` ${fmt.badge('步骤 5/6', theme.bgBlue)} ${fmt.title('清理语言文件')}`) + console.log() + await cleanLanguageFiles() + console.log() + + console.log(` ${fmt.badge('步骤 6/7', theme.bgBlue)} ${fmt.title('清理快速入口')}`) + console.log() + await cleanFastEnterComponent() + console.log() + + console.log(` ${fmt.badge('步骤 7/7', theme.bgBlue)} ${fmt.title('更新菜单接口')}`) + console.log() + await updateMenuApi() + + // 显示统计信息 + await showStats() + + // 显示成功横幅 + createSuccessBanner() +} + +main().catch((err) => { + console.log() + console.log(` ${icons.error} ${fmt.error('清理脚本执行出错')}`) + console.log(` ${fmt.dim('错误详情: ' + err)}`) + console.log() + process.exit(1) +}) diff --git a/adminSystem/src/App.vue b/adminSystem/src/App.vue new file mode 100644 index 0000000..3433913 --- /dev/null +++ b/adminSystem/src/App.vue @@ -0,0 +1,34 @@ + + + diff --git a/adminSystem/src/api/auth.ts b/adminSystem/src/api/auth.ts new file mode 100644 index 0000000..9dc7b6a --- /dev/null +++ b/adminSystem/src/api/auth.ts @@ -0,0 +1,29 @@ +import request from '@/utils/http' + +/** + * 登录 + * @param params 登录参数 + * @returns 登录响应 + */ +export function fetchLogin(params: Api.Auth.LoginParams) { + return request.post({ + url: '/api/auth/login', + params + // showSuccessMessage: true // 显示成功消息 + // showErrorMessage: false // 不显示错误消息 + }) +} + +/** + * 获取用户信息 + * @returns 用户信息 + */ +export function fetchGetUserInfo() { + return request.get({ + url: '/api/user/info' + // 自定义请求头 + // headers: { + // 'X-Custom-Header': 'your-custom-value' + // } + }) +} diff --git a/adminSystem/src/api/system-manage.ts b/adminSystem/src/api/system-manage.ts new file mode 100644 index 0000000..8f4a8e6 --- /dev/null +++ b/adminSystem/src/api/system-manage.ts @@ -0,0 +1,25 @@ +import request from '@/utils/http' +import { AppRouteRecord } from '@/types/router' + +// 获取用户列表 +export function fetchGetUserList(params: Api.SystemManage.UserSearchParams) { + return request.get({ + url: '/api/user/list', + params + }) +} + +// 获取角色列表 +export function fetchGetRoleList(params: Api.SystemManage.RoleSearchParams) { + return request.get({ + url: '/api/role/list', + params + }) +} + +// 获取菜单列表 +export function fetchGetMenuList() { + return request.get({ + url: '/api/v3/system/menus/simple' + }) +} diff --git a/adminSystem/src/assets/images/avatar/avatar.webp b/adminSystem/src/assets/images/avatar/avatar.webp new file mode 100644 index 0000000000000000000000000000000000000000..bea307b7d2e5e1a9d952d834eb57f73605dc55b8 GIT binary patch literal 954 zcmV;r14aB&Nk&Gp0{{S5MM6+kP&go_0{{Rp6abw8Do_AW06w)$ok}MpBB89-ZP?%z ziD>{53Yw2F8^s*TeR(t1;sau?)6x+IJ1ULup4dPqlKRaw%L00&PyRBV@TRirkLMoh znecI$mizfs;B~7~IjdMHs2{A`^5xH2WwlFsv{w~GUrD-c6mAR;p8@&L1l{|oRAzySXHiNX^g1uHD- zk>h_3$=>CvsNexHz6NhKDop!@&$-)@k^G zpxC;k(_I+TKnb=kfcV<3(d@OeA$m8)Ux;2hwn++&N9ggUrswPzKFBA>H1|z9H9&|78oYJ6 zqmYWjF_7HcNYd8We?h}xgx?|h{o>j~Y6Ort0-Tr&f;blhrY6~brlb8`aT?w+$`-AaIO=h`ieN z$~Gg-mXxYMelEHs`4HK%%m9=g4(z;v{W(jv8Yy0+*!MmE7Wb&l>IMYrKWC`)P-M2m zu$RqPHPng}*nD$a!U%{{NAQRJtAxj3Skt$rNr5Sz(*ys48Sm7CQ28#6@u$SQp8jq1 z)0|6ELT|Zgn}T|MFJ^9l$ZpXeQ$e+^QI50?#DWtekW>TGc)Xar_&#%;r9pb{&G`?s zaP-tYMb^D)AT=a2z3RNn!)}O2cKg(?2+hD+pV;B^mgx83f_)BBT#K1H) cmY(20IDjVb)DMN`D2T(p^ZM$jri0Q@7_2mk;8 literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/avatar/avatar1.webp b/adminSystem/src/assets/images/avatar/avatar1.webp new file mode 100644 index 0000000000000000000000000000000000000000..68e256c297cea6017efb892509f005290f32c95f GIT binary patch literal 2296 zcmVLHNCsINV&i=yrkQTpUuSn>Acb z2FE|`v?5W%`1-J1k%&b^{1D+E*v&*cgPXpOkC1j6c3zl~THPT33XotPrLtyQ6T5WG zK)!TAT}RO#s23>vzA4R_R4Z`A z)!tkNowc|9f8G#J-=@;b?8g;}XfMv3vXpQ%%8H}JEd>Q-E=Is*dOG0dKP#mpzK6Zu z!~hOswhdJQ>~$JV9YE{yxG0G7eyp!4WjSqX4`vFrIzpIQ0C7!XfcamHM$sc`M2fb8 zp?r)l(JQ^6s8qm)T%}1_SBx8Ei&#F=-z9}B6N}fuspI`o)nIez>LrpO#ZCv{{2?o7 zizMB-j`ht%neuFw9`x-5QLn<^-`6#2Q&Jjy+o`+KTIQK8(?v)43qCH3lBIY}J^8V+ z!&1a_htv&I@_M)c0092^2zg+Z2FA6yE){J(rlOXGqB`_f57m^A&4PKhbc!KFs7OB8 zdlaLr7TvuJI%#4#-P7{r$I-Vawb#sYrY1ZHul|y?8{3F>u|DwD9~|=ZB=TKNp#;aH z6p{8M)-)<+c{^=m6B0X{RudGJjVD&&L_)4Q_O-UM8aHDF436jm=6;o62nG$yl1N%V z^US=K*lNl}uT*xK!uV;kLRBU>FcxuN!aJ-6^T-zU<4BjZHoo!!CG`NKe=YJ;2G_dxc7RRuC!*ki8f7LL%n`-+KYn3a!J?P@;tfvk2Nst5@wK3y zRdIUSYB8(CZTDhk8nxBMCfFOiau)hb=Rx7ub2hR3WL{Hyrpsf+J+hGLMDG-~woLz+c}Z@!7eEkAC9e9%w>(!zlJ@E3xS^#||H1lMw>!0H5qz`? zDTk_akMd^!5{RFXFk>aW@yCUb(ne!t0QU(B9Kw(BX97mmD2irsOwo)JKe)-C9>6-W z8>zQiA&goy3?**OV)g6ha!+LWYa$kEDvBX5cFt_J}Y0=#0<)9Gxp~2QlLcB8}FqW>Rt=Zky2M5|a z;Hc#Pb0Ak4j|G(TYytIeThz9Q7q9nW1+bIw%qZ{hkGJdCN^(2I&!>>*jiM;zsNsay zfD%f*gMDH+xlIIpl2^zsEveXG-)Itxrc5f{VG z2+%x%J-=#LxVgp3INtBtb9Dls=&UII?9k3BspvyU-yDWy#ZZ~p{*3H`GxnG%ZA&CL z&*2_n6+ig<_r zr$au4SfLz_%DUXgc@3zY%{O#}L*SV!h@$ z0&J2lMSWRS?_2t+t%IovykIn^Gf0E(4)1S?`Q&xoPeYMB3I7AQVb*GGTG&qx)z35` zm-*_NYHX|F!A=SFfAm+@A_k_kST8zZ^As*W84Ywga!W-8E;52QU#|P1>A>0AG0ZKh z1{j-VQFdICVO6OER;VL$V>bR5N9PP03GmD;8Qdn}J)%-_?J)}HeZciZ5q@(VMY4g( z+?!k6V8t}Nw0vuxbc5?JCw4MK7peL5#PnJHvA}FX`~lUGz3J zY|qT!%$9q#ltNvl4zfC+1!n>208_(%8;BBBv?g=;kmh|_nCPRCC&3y^hj$-@-t&>} z-b^A#l{w#iwS?_Z7@c;6zG?p5>zH=`r^z&UF!0@+o>6G)n+?TjkXzz577X8g2C<5o SVD2lzkuUx<_ZG<9l>h)e2Ygfj literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/avatar/avatar10.webp b/adminSystem/src/assets/images/avatar/avatar10.webp new file mode 100644 index 0000000000000000000000000000000000000000..a813d4c23ca9d2945fa089f0e4281a7e6e122e42 GIT binary patch literal 1410 zcmV-|1%3KbNk&F`1pok7MM6+kP&goN1pok0BmkWODxd(M06w);rA;R!qoSu4ia_8N ziDxRj@G(#c8w@MiZ2Pm1xtXg&SV}tO*upn_$e3T{3IKsG)2P7zl)<|FlG0beLZ66? zP8MKY$g2W!|C9%Jz7n>$67R(*k0AH;%P<`vmTFO)=V##pXZCZ)gtN(Z%p*SC`lfMv zD>&oYT&*hXU^Rn_m=qyJP9*z79qaCF(f*3BmcvomuMR=u8kHme^Vh+Y#SverH3t|FRmiPRS&TRUoCKe-bsYMg$7@O?MPgGwcid>XA$|n6Z=L) zZ2uB6qou3e=(axuwzL2Vp-2eWFAM$_IkRILs9hSH#S0Z+MXb;wb8oKdBte3;R)bA} z*+BGDjYM<{i3fa8{Zlse2Nb(xOK+^ybEndK`Zlp`kDA%hMHUtv;J0j=R{~n*D$iXB z&!JSJ=<`@=^kw-&i^Bsa0`G0K_b+M}`aMe#zB&dJWAP(c47c>965b2)B(4!Vx4$; zw1H=hX#>fy7P?nk_rl#aH{h1yQ$2BN$smP1u-VbsMnWv11? zLp058?xk1an|Sw(?NjDb;uebjVkWHHiQ7W(f`P#D#fli5R=&I_)-XU?fc6SBhQ?kN zJ$AQ42J^$gWWE%oe-N;M|ApbrPsxz_(cK-~ZAFgxPE2^B`Q75YhbMy^7Pw~YD-ChS zA=>F9_HN9N3FH?)0f>*-9{l#atdGpurvFT^8H)aFa0%PFn~F3`ubg8gY$SI3_!~2` zS_l(^H=T)7S7kzz(;dvwpf17sUZ}3lKcvMGGLfTwlHX5cAC;>SC~wT_CZ)TPLJ|+^ zO1^m4YAnp%qoe6yc=?;Ew9>7%+BplwY*&G+-T%ccSxkn1Qw7Y2OFWn*`da)d=D&$8 zPbb~qkF!m1wX=~@J>R*@ZYZ}fr6gbA|E>MzHvxRQN8k;s6giiV7hzBm=J2h|Fq!!k zd3*yobrI{Cf)}+vu8w>&2vWX5W68fofgrK9qFsRY-2?*b~wc7TUc73C>=31jaEDl4iGgxUWZ zB4rTdKzTY{)gGK&8Am&B2>R;B9wKiG*l5R=A=#t?3mmUkxUGS7kn QTF;h&;#lF?m{x!Q0CCo}*Z=?k literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/avatar/avatar2.webp b/adminSystem/src/assets/images/avatar/avatar2.webp new file mode 100644 index 0000000000000000000000000000000000000000..6716e3ff9a7c07198562500a8b7b15d86eddd92a GIT binary patch literal 1214 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 SOT){6x=sa6x ziDv-LJda-J@i#;hzW8~^&ycs+x+Z)S_h@HPs{&NWIsvF}Yf|wew7S9Za;U>aQ$~y5id98~wmJMzcHjDBq)*uuBf~SLu z)Gp7IuZx!uI9P=5&_Dqy#yuEZD#{4@MMZH0ciM^|XF*;3r z9;A{NP`bRGi#ZJEeNWg`G5RMK9xvPpo%yTs_LyO5m9BZ6M8d=6DrzKBk#26MucKg% z@Jmlisk&6TglV1uE@+Mi6dU?WTlj^OWWD+VusOAR0QO@7&Is!osAf6}pPg{NW71Lz z;rRW1v6D6$EyDKb`U3Y#iU6JgDBoZ>M~GrxedkT`+jJu#XwKj42Uj3@#eB16DhPMe z?b(<`( zs`~_!4&_XB-I(Go!Ti!(B zzX28-dwQ8RJL`7&KAUMlIpDVwTj)zHMR}xDz-D@3~Cc z7|ZkGAVEdXA(ao{SKLu?NRz`qi;$@P>N24l+deOf>s))>An)p$#2X!_5;0xOako)X zjIOe+FA+F+H}$q69Y(8V+=}dvt+qbRSF%REpd_z<{gNTS|1wDRih&;=->ZRCM90dv%N9SSzr(f;PM-%fXrH3Pfe2f(DQ2NV&X2&-DORYw}T--4;V);q5 z_0DY^&XL%`xt(>cWVu58mGS#f*E!W%9W)`^Ht({sb?}UPP-X{41Jd+OelWn1DB=!? z?MC>k2#5dG-RGm{KTf5t%~`eqSg6fR1{uXI1M|uCGp8g4^^df%E{456Kx`7P(>#EL zmhGFzWA7={)deTE9PtU4F*C@=vOzZKQHFT0AgxVwj1%kfZW*%UC{wbm;GRO6K_yc< zw+EhfXW12v2HeMBD0!Hq=)pPKt|b&p`+N5s*oO##&@~ZAhA7o3S52Pz{bcveBBC5r zCvhxa8Nh;#y@aO<5eJyW?$Hl}R1WvJQx&b&?*|4N4T}!E?@yxeE3b2sV1`a@^R+7J zp27xGD+@x*xmx7d&{M%Yfd#$BB*qzx1LzqTkQa0)>ycf>%P|WAHa3;I)bi6;o@^&8 zlF!9&kvZXgXme;5J9SggsLI9x!bbkq`v&rTj4TS4=%It-3h6O>)z;jL+26!mg@0s7 z{4(NDv3IR6vYgZOFlTl!c!b?aLBP6L#|@u(xpJ83IMLF?7);-IWQly}m|w*of%ad$ zs4&NHOL!Vvik=DMMaQ-=WnunMXR#%% zIGbDlMeFoEBXYga#NI7n8!Wj(pa!rZe>u0q3$8d;_hEE_F!k`bcamCdsV5*a6aolrOAz(+B zd@P)%Sve@b1T5Gzk>0D?uqoxzL9g;Hq#>W|iLtr^A-5BQTpT9ebo8522y|Yi&}N}+ z!nlr^T4DNF?&R$Cvv9rm^Me9(!ik`_w@ulxS$XEX<#KKx{wypTfIc~YJvA0GHEjBa z{tEX`KyB>SnrEk(@oucu?SqXbBaEcn5w^tTc2WGK(%4D)aa56stfLL0kuMEM7Rqi; zo`xQal&o-#zU;IGUcZdFg?D_jdE*(s*c%p)6)*r98xXMy&ILWlQaYSn60FD<1I}dkS@TL!3$rhUX&agD&Hyk%~IJImH>Onl3MQU|Ay%bPf&#ECZX4~J>IhG$r&FUr~Xpd&X)r{G|={i?b_HnA@GVU z)1>)kALc5Yj)mf+#4Bd2-y+^^2?%cJf4F uD!N*}&DvQ+z%4JRZt z**wNaQ)XNFP~Hyc-5LfbIC&@l{_)m6_A6qduzFFQLe7hG z+`Dg7`MP=8PMs#W z>)Zvn?0Q`J3a~$cdz%M6qCYPRal2tH)ADr7pFDSr0hhEecfKJAnD?cGv8&)tngt<% zBEa;{SzSMNkCB(s_+f1EmeKCik^88@dGSs1>7b6hrH2O!%;F^AILl`PB#npS3=mrv ztu!z!I`*4f;;#|N@y$x9puJq@e%MLELvFmQ$7sQp+jYi8zXn z7!yB{(N+rF_;=ECz2YtbVQ{P8&hmx-SM^Px`+Fx?{VVD?*kb%yULK5fou&W;zx@57 ztjN5fd9K(%4A*ppn$C~0uwAicQHGUJqaXtTq-SEO3i*S5UtzkmT-;2S-*yKx^S4)p zu(xtY`uuGFdnEcF#&D-^5r5^8ea?Ikb(enTfiNd*6A`*r_w3nuP4|nOjv4Ar7>Bn* oum=sD9}=@(JYWHzN=bN_eTt*WCui4r!^|MQ?*(KtUpN2&0LE>RZvX%Q literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/avatar/avatar7.webp b/adminSystem/src/assets/images/avatar/avatar7.webp new file mode 100644 index 0000000000000000000000000000000000000000..e5ef6fea28c4dea1f9e10e78c8183bbb544b75d6 GIT binary patch literal 2712 zcmV;J3TO3FNk&GH3IG6CMM6+kP&goj3IG6bGyt6eDxd(M06w);p-ZPFBO<9-OL*WG zi9i_aoN0I-4^TUVIodrc|E|84pR(K^4Ye}2him)-9_C)Uf2WJ@`4U!&jCB@!jQSWf zkWi1W!e|7=uqM|}&jWCK0bJPHdljLAMtSOBhK2FL9vIJwZ*jR0^qz{%o|;eu@jIID z>1Dho=M;OiIBQ6k90N7)U-LUB4fNbnB zcVd!#l@ge#a^5^>ovyb^=oxMdg}d1U^l`2Fm~d{`GrP+hqwRdlsSp zZ-P@AID|3okzavUmSMCE8gScpMng0Q`5{KtM0ZUi0ncfw=fKTHEdntzAfZJM&W&Uv z67K-k4KmgBg(%$Njk|Jg-*xprnCV_zKfhnobWW_!Q|jolDX+OMrq@H(J)qOVrR-qv zRIBNovB7=pht`u{?#HO!58IuZqG!54`tQ%x7=FyAh}uj_ZRsSbgQ2Z1#W=b?n({iD zm$Y-O(G)Itc^B80u=Sua!8J2(sb2BUsv8#G)g?fpX(OpesA<(_`VsuMZ6dfL41iYJ z?*M;4b?t@DbYfq${(Al({l4n!l089uXnn-u$MA2MX|98@2|Cx#>)`8d($l@QFME%7 z_6}QMOoeB?j@rwb*TH=HoDV(FiLvgaLY2@MYNJ=k3TkS{3csOEw_&C-HxHy;ajX(Z z@#lxXph1y}+iOME!-6i8f&P2>gA0==<@-?sKQ{II&j$D`7MMhkLpi2;N%;HPy+G{o z000uB!pDPiCpwuSQQiLY##czfeQr3R&d!)&9!I35n8R!_q{}a)ZBY|b^-u<_=bdjLM zsH;7oYDXr^TFEo<|H}DS%GT1C0L2i5=EqD1g9i2J^W;u>#;j@z=K^hzFU= zYY2QMrIAKg`G1?4OlL+SauH1y`A{Uz#84Uv44XbR0-O9-ol{Wgj|tx$nKLP-P2du% z-Q0jz6G7SVI+1s!A4*xZp5N|eZYSJHTa=>)Ajvyi?EeB#w59bKeTsO@(I;I(WI77R zTk~JMwUgwE@w2~AD^;ya94Kp&WMaW0WwLS@H`8wZvU0zjb@itK&I(FIx@VOp7hB0H zotLS1e03mJ6E`f%jAQB%^&LSPkvnigLijZK-J)3ej9wE3&g(7ejXm+7C@$Tjez_I- z&tVa7hX+KoAg}8LEcb@&UGwPMMlWWtOM7&0LePzZIRAMRmZmAs1`e%T@SSGa=*OGY zhQzw}Ca-M_vl&mQDqK~wClFH;H49m0Y^Ym{L4&A ztXLwKYl_@y2ljr2kkQYC=V5w0{+F69RiFdTm)%T&!mfzP-kw#`aCmmRG$H1>hu-6$F77UAgTvGa;v!U9uan0`(ci0>}JF8n)U zn-=c1mA3i;uD8W=(^j(nz4X`EN%~rB&EFjc;cV(?)IMy<#JIAZJKCn2H!jd#+FP3d zh|Te*k9)A5x_gw)Y-n@x$ zX==z!&?XI>ePW83uOwKEkDLxe*zeJA4Pq9#;q%y~fS0-v=()A!rQFSY%-}3u_+s~H zW9zimEqBTB=n_B`hS84Ispf(XDkch~Aqqj;#aFJNd-kp*#*uNX>22y$d5{l&6GXjo zj>~j?H5qFF3f<<()y8WFAO-OjT6Bgy#(Rr=E4LU_h_>6k79MC*;=Qq*6sN;;TC+an#IgsR6KrAO z$c@_{eBZ9#Y{_X^v3H=kdKtigpF(Gy(*r=wmktt#QMs28C30ForRlH#j}jMaWuk@E zA_pE51~<1C0n?zwLyZVm9FlRO5^l`sqbf$ri^uN7MVKB^fThgOshG+$Pouyye&KMJ zbG2%FWThe&(<80fo5owGc^_cOzvBH@`F?*~*BqZnd>}Yoo_nqw(`pk)V z*Z@dqLMOC}XJjR@tX1aZzBlMg5_@O^19uBchc*Buw}AJhXJgG{jpTle SlnZDgHxy*uU+B%U0002~4@gr0 literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/avatar/avatar8.webp b/adminSystem/src/assets/images/avatar/avatar8.webp new file mode 100644 index 0000000000000000000000000000000000000000..b66e48f7b056cc570d368c4e0f5d962216d29971 GIT binary patch literal 3946 zcmV-w50&szNk&Fu4*&pHMM6+kP&gn~4*&p=LI9lsDxd(M06w)!ok}MpBO)$%ED+!p ziAHYgkE2>AV;280`4#PjfPUNfL!xx^TRYIl=I{6(j@?MSz<$I2-22OVFaJBfYv=*e z!D-ys9f;KbYksWrKkfc>J16;rs9Vw9B%4RoEMCw9##=|ai*&1L@Y?**fo zD{1qqz7&A&eAtXLq7n=VC^`~4`Z^nsfX^mNT}!R`C*E&?y``%^eTI`t&w>fvaTx{v>Ivj76Z?N|FQW#_A2F=Llk+uBUuN7@CAjD`oMPNpuJ$yt)E@Z(w^P51ONTLullzuZ zrv_Lh8)h;>>Se3$OD9je4Y-ApiDZCkayYtJfwAEd<((&T4%y=x!mZFKg4&#(6dig$g#u!1zb{B5=IGu1 z`SKKmKybXAqz_EaGm{w*#`O|j-iuv^J_tTK1(jy0AE;u@vzrJ&T2TqY-WyBG`3#wz zB&h?dBN?zV0_}eg1@8a0DgJq0#>MA_!QPV4iDM)=32n8c-ho>8?W-KP_jT>5kyS)$ z*|hs1_G=oG;!Og?Dgnt(hW*1Eic>yUf_+RPB6h?=b<4VI#$RY86HR@*PFFoVJ?HKE z^x0MgFxAGA)1Oxh1FNVVTZlM&kJPWQHqxZzqMSwOnK!DY6^>_mSg83SxU@w^se!W7 zx#EXo-R<(Yf|&+Q6mVr{_vun^Llh^g0g_vfyiYKl#E7kNZK+NTWl$L47Piu<_b=Xx{Bkfuwb+`&9l zWoN((WlUcf49lHL=pOEJYeI%ht<-xn;M&pmVAU@L2Fcb|BmWq-o_HYIiiDe@npbCf zo$|OgymIpdjPf(}i7Uq!;2e~QBJe)(w?^QuGQxipsJ{9}@4sWNb2b?Es?Og{m+R zurAukd2LD%?Zk-lC{hLk9&=n?VqYn9L(T#vRf-H@(b@r61$Xu7+~MIymx)6^`qI(! zaDCn08VeTL0IBR^KLOZ^6^Sp z?t?gbrQeXLQ<>-ZZ41b-`FzC|7?1=f}ze^o^5OG1!%3K1R5A)KF2ko4s$*9<>d{9JKkugEJVx*=N1Mj$q&=(@lRp&9gtA|aQ_>7w+<*o^+ zA_3>JFkkT8C&e(q11cy3#s$fg{80Ojp;x!0a!dPeM}Gos>-24z<@)ANhsL6f2Y3Q~ z8GmTT5k_c&XWtH?fweQkF#`-hm^FKOvW-4)YU(#Lq}#ENHM#mgmRAHmC|zJ=?^g40 zQZ8h-qXiCVTW(#NE&pjWmR1RM0GIGJ0AI|Q03SULV0(G-KB?0z;VfVLlTYI{4=lul z_8wo?($i2GByl72h2N!5P*w=~M^IA7V0|ZQ=yWUajx8EK?rKSN$ z1ft(k_oA&*)^Bt*Kkwk-ZaE0yN*<(rO^&zobUQapexBnZLRuKmS#NNy&iMwft z(Ohmpscr7+&|{JVW82pNasO1;*1Y`D;UxN@SeU7=h>?bSu;=+eR1e45dEb*e7u@lJ zJFJ2<*i~ayPL4K;eODIs_A=m=1J3{KrV?AwLLo&T`ya8VSLxSDt+wf9l;t-{6(B>h zg8e)-pO1bQk>Kt8MMCodH?abtycc!ONn%sg){g#9ulL`I*++ZahdZ2eq0h0yhr|Q8 zVsfB{vdch@6aA!?`#4sm{Dai6t1>*#_&(5*bTy4YnUqbYvjQN^d1>obz=TbwAQU2c1GziBJ`p=ta2z0p%AbSJ}t2Sw(P283$_tV)zoQtxD!|`fqN-J2b z#ud#-Okk1d&|Cc&Mo*TLB`1p4V;u9?p8aF-mq-RP?FZA*(5Y0ap-gLm+bsG=VTAc-6+w`cMi{D89SL*j~Tvh zmqP(-L&G5`#ex$`*IHq`+mn;EG$m?IVLWO)(9-XYdpDnkePBXf(vr8T-!C=tKb;Ub z8Popf$rdtFE2qJQ)Eir^j??2+3cbVGNoVn}>|Wg4M4fa9aO{hSz!mqgy-vM;+| zXd|wj98{<`mPD#lO>(#}jTUcYx)n5OokaD`8~h;nUMPae}*@;0e8>jNj;!al2*bhg7ES>5j@2m<6dMi-ih zekTl^@EyrCV}ibIuE<^$rlh>1dFGPdAvN|dz0kmO$-noqA+>nLjK=hmht`D5g5Y_@ z^Ym4Y@Z!|gfMd1m`fx7;wzUv+XuQAl18_oSKCC1t%dSpJ?%Z=1c59Bt=e`oP5O z?$0n^)zU_B$?apseefN)i6P`w7yu3|5#g~Xii8uLK7^~N1rcd2abo@QMgKj}f59sy z20)+xbeDx3>7I~a0BG|DueJH2i=jlMXkMIZeCpfycd|Q9r_EykRH_$vWc%2MF?N@1 zu-!B;FJ_U~ah%P6SZ7FV%yjbSNv--dUCnr-%{AJb;{Zm1JaHgN8w(6IgS}w$= ziqhi|h@yKav^epWier7rQB@~Mi8J;<&+h7!WePd!>@TSm^xJHDG(3fa)uv>PWo|Uw z5mmV=$+3WjUu2!(sB~20ime6knT6JnC`l6sjm94W>b;7;oe9d%ib_0Y)%w`#)eAge zq|HCP;eogtSbJAi{`V!iFxTcE#0~Ct^NgFF)&W5GOO7_~3k?Iu82L4O*NwvxAD2m) zGo>`_T$yi>XVunS$OS)XhDbjbz-Zwn{?6_6ir@vf=i*6^7447}SqNvY4YQ??eVYAf zEJM}5Xn6P9n@dyw)qGvtIq>5NwccK%2_*8OMVuq9>*oDSlB$*{{Ua|GqG_xewi1q~ z6&rL2`4q+V3W&~#(gzO|0J*?8y$*9gYCg+=RHxxs!NC=-g7iS{%PiFu4b zJ&#=~M#(0Ud@(9lqxEX7@*>0ccHHlwC`ZcJggND<@9cfdIL(Q#T>+h`Zh*Ma>=XAo z?e4CnmZPEz@?nG@2)O|1_ey{`*RSdrWLze$TGqw71u#*l*k&5sUcs3^uriOhj7p~8 zcU#dXqi?xN?($MkMk?Sv9*14O<%ly}tB4sgRvYy+2h7tdz@xdL_p>fWkZwRQ-Q1~{ zzs$woNsJ(pqAa`=4Y1=OsSwBvn*WIz!KgCL=IXJwV{_M?gKokGB@Do0FT@+12~8*V z1vf9va*%v2ug%9O)V7s_2Zb!;jsB8GQ+M5>aaA+z+eeH-Z@Eko=;oKTn=(q`z;I*u zL;V_VJ;ZE+QBf3>Mc;?X6p?4be@d+=e`P27SlOU#b*`8X#i!|;j91v E044g>D*ylh literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/avatar/avatar9.webp b/adminSystem/src/assets/images/avatar/avatar9.webp new file mode 100644 index 0000000000000000000000000000000000000000..7974139777dcf5db9848b8f0b97b58708d60d369 GIT binary patch literal 1680 zcmV;B25Cq`s!Rw6r*sJG2T- zxVvRExAV}fjI%u($cwje4h=Qh8M^Vhm$-}w&Q^=5w2y(KN)o9n&3k|tlSOs7*31#L zZd*A}kBRj5Ao@<*ftTKbJz|QgG6yI1)!FB5ojbeD5SIT??x5_85>vF3(iY~_s}B6z zJIL03h+X)vz5D?ncg&2BI7M|1dFC`>e0JBim5qthgnd;jgMITvIsJRf`(5IKpu&FX z-rCs3S~Eqe$321H8YQh+4o=)+a^L}065GaGUCEK0%hpy+*Qx>$m=Rv}gtBWa=77lX z(VYa&S4AY~L9+k={`NoGgSxyFZfl)Pi^vA#KNDL6dX8Q>us5c5t;6L0o&x6W@m%lA zDM)$!t-!khhGc^`eu!>cuJy#x%f0VI5!yaa=td}Xunnnhr8ev;uMHIhcr0J$XyqK9 zuX57K{X^gJE0uk0OB+EWP-c7ns_RMON}bt%QVj{Q$m;#|WErNiIZFFI{kvAmC0JDA z6S~aC#;gaT!rVstFalV1u>i%0BG3(ck`%1_k7)7_k4dLbflBWF-$=y zYXuFm?Pkkw>>-p*ze|ub5=lw7qz?%ivp7%e~o)zuOB^QE*8~1b{~M=$X$;g4OB&<`_pQ(fAcH!qE9%DO$8BLT*BZq0o#qHcMiMyX~<| z-=+m)v%h3{wy>hD`#}La=L=Ej{j!TN*UL@gmGY6yNO_SKe0~V$KMCY{uAJ4ke=00S z#(Ss89O&bj<#0={Y6sz^ccLBU{iacl0AlStaKAP5^H&%N#arv<9wubKsR@^3#Bh3j zH^cK&-QW8*&El;j|62<;pvqeA-&I-=$SY3PHw%JH{W2HBMLgw~VZOlbad0jQB0lI? z&A2OQ_kK9>21w6V#o%fNugN!cbI}(?Mnr>8{3YLGv|JBl>tsulVv@2-!G>Bp(?L%z z_4KMfeT23VcUa&**Qq9gpKCv+thWy@(5FZ;SP-pYt;n#N8hgyGcT~yp0o7wWy%$XW zaP2yYd?+xSzH1i5u=5^{bo z<@blJ^<1?6)cNf7eacQ)dqC94Z}gOf7S(g?b+Z=2PdS8(tvK#JzU@=G-<7lJE^qf| zB`U!>-htj`Lio0dQ+Ic}ohghs09r;1A1!tJ0XI!YVxlj%Az=?yczeG5GPnkT{O zS^iGH_&v0;c>ZPbqB~?md!#IKb(FKUmVRUw=bZ%(nx>OTGVw(Tc|O;5oLA5MX`wQgQMaiXBB zpcHtwlRdTvXZ+CKlb4)N9T#5@t0>l_Y-OHSL)^qXurPW*__rJjBN4{NpNd!kblcd- a+aN;YfSUTyaGIA8bqP%u?tI%K&_Do!2TN1{ literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/ceremony/hb.png b/adminSystem/src/assets/images/ceremony/hb.png new file mode 100644 index 0000000000000000000000000000000000000000..41033245879f1051f34c33766bb6fdb4fffb38a3 GIT binary patch literal 2275 zcmcImS5#B!8a)ttO(dY8U=WaA1Y@Yu5|SVSLls*nQRyN9At)NUNHK_l2oV%TMHq%6 zW~HUV&dd2T-CeP=QYuma z0LbETXb%7o0!tx40s`K`s*ZSpjlH{z7Y1zkPUy`(6yFK+V`I>pEOS2QpB60u2atRa z*&=|CHo&?cCD<6u+7|PZwe=5v|Ia}Tf6!8(^HbId%`rgn(T)P-2M!tue5VWrG=c1Y zkT+Nd_-qS47W*&AGXD__%+_x6kNBj)76=aZn@?`(2$HZY1yh1oaM~J2Z-T)_>suDMOUNnq>hN9D^-_6J|>Uy(NO0(zje=p;q63A<>}zMlWb#0TvU62S8ac z07zD!Aq9aSF*wG<)f1pZWe1pPJ%2*G6%G6F_GRUjI6r$kBgt=^G5T*;Bzg0ljC=>^ zTA$bQySC>KX({n%kGSrXof++Ks6K%aUFVLzA`*S^mMFw_IJ=zSVXphv=Z`g|v}=@v zkW=o5F-T**U1gO1`|gn_LJ|OoE#T4iUh%`+ku$z3_HZ#yp`3kNGF)E^iVL*YYTQv? z8+LCYGgG%>Y?9BXh&0l}l}5J}sGqsxre+&;o>0IpvWi1UB>&w09{yeJikG(;SxTLS z#Wn6tiR!tOM!#_~RsUW`wJIH9zsm|cIN*~Rvmf1v}8ir>tu{H8@G7<-_?S9f4WqpX>ZAj-RS!{3iHgYJB zUwD;AvY_vo`WkkWk(^XsHys=n)m2qKB41Wbsh5zOEWl?xo1jVXChu6YO<~L&VFjYw zw#cTr@2bd+kbW!cXN?C?T-A;JvN^iHmR^4TNyPNjBe~X$BU#ALLS7$a{CvEOYDRRV zG6%F*{<3bL?KU@yIzv>b<7oQjr)z$E;WZSrh^J8kV~U^F+JusB<=9WDqlDcRbn?lo zDw^VpRVgbFTqxPpSSGWpx|7=Vg`GJH({vk>Wl(p0=&mBKY>r9U9Y&BHDmvuUl><-- zke+~Sm&f6DQ|Cw)7o~azQXhx8IGA?04Egj`Qze(paIRz3;vJsB&Y1brzu-=qc%C*W zd2@Bwt3g;?GotQhY6qXmmG&yXwFUU)Yf$?(kc(W`pdCN5-v1v zToE&Uf$CjP!!rM9eto^UnAX)6u2_d=JoFBbCuX!>Up9};7BjBz&PorOhBeMurH0=f zJhhx@)-9aJV>Kk5tAI5H(RWDg%UMq1Vdil_oj?8Cn($3MxV$#rSQXuVp<QP8WMB>}Vqe`-kf-H`C_x$6V+`uqTu6i7O9 zesm99cek;XW0C5kb#mWzqqrL(Id-*-nH3{LNVOzgqb`tZiz|gS zah?EmIB}!yIU4$WTNq*NILvI~9UMQ^rq$Ss{Djx zCuivMP<2n!v=%s=P&EFXkiv{8Pww@7q^4wA($G!y(@*=7XW2erCPmTxN}3%W@7J@n zaUixi-xr&6qZq&N&WHO&%gqHbB?%`2iTK}Fk8vLLzZD8MFBRvK-fQCu49rUh$Mx+uW!0jUCcU-x}#WRz?c^S|49F*g^)J36r3gg%c2}Kte-E~7qe#2lqfT1 zofWC(7}s;Jxq}l?&*3doa{VHgsRu_M2W_J`=m3<@h2QhDWJ^j!7Kdz;Bt^7g4C!;^ rtK9?{oKY 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

yjkcOw&(>Y8mz^;5!^gv~u``#^d4Gx|bp z2-7XGTS)^qhf7!w*}02Gj=X#OF@kb6Jt3zqO+zHUi|*y6rT)zw2fWpG5HATLaEHBA?3FMMUbKN! z#PTx@LRbQWSrGV>{xDg;Y&mz%O`b0->jK)iZ$3px^C{9_{ufhP&J*iK#sD|4mEY65 zo_)#pd^kuy=pjs7EPZ($mXiA4=KVU~KK%qdoT$y{+sF>raM|L3$68%+)I;OfUJTDx z1U>Y4LD0a6u&j$I!_&)rdJZMen2S(UBFCq5k=S`7fSS^^=A=x(J*#i zKz~~J*K*g#S=`a;a@2hrk*YK539RJvg8R}@o;E{PoRRvhvd?og-ce(w(-^A772(B( zwL2heplT-AQ4b4Hp0}RR@qK_+3oDXt)Qd>>d`CW+gt~#ZDk|uueQ--tz-xBiqRf(p zSffC>me&9Q_wx4x z#)Qrvl1BIk!nP|C;SW46WI9zBNIC596gbh(Um)?0RRV@0Qr)AD2Mixv&BN8B^nL0xEYRQ+MWL)?U}O`I{8z}8Q? zqG61P(MtfE8g1vOCeR!PQzyS$q+)y+-wzSkduvQVFUmM|rHAQycjo0CzZDu)rXQXWDzDqA%HPG7qYby8MxfS z>2kznxxt&D$SV6{@UgaOVu)ace0YO7ty}dTMy^_LW@vip+jR!lQOLYfcUR}c^Y@!Jnct35&-L@u9|L%xY>y6!J@{c3l!bY^F&>hg%@dSqG{+5` zE4-aaWE0hb+H_tHF}e*kiqSi+VPyDuDOBh%0Q9PS8x*jW{fsR42v9aN&nPoT-pD;Mch2^&mj&z{}% z?ao99Xsn)nJGGiN&%)>Y*vrk5Qd)>~eENEgD#>kYk?|f?-Dc2*)GlQs5fWw2%`c@1 zquNsRdGT*p7sf+aGhl!xV{-_s@?%#S8;Q64tRn2_{uzADxkbqN!l&w_!_$_;R zdnsX>lMrYouxu71`|g>Q3b*oO4VgPmk9>qyZ|=MVra^f>#y@Cjo3Le`o&ytIK5lE8sW9IS|@f;Q>AtCT=tq~<&h4j>sRb|(%9~?-avmzJm z(1;a^3(S5?{Nf<=?U3i=pJL|ad65!sGoKsxu4*RAB1&0F-Mi^-asgy-m6dR=Z{s3z zavmTR&xu&hAESu(?TAP1rOXTC!xu7C0j{|8YD=rQ4Ws+FS?yX}djleb%}G-Eu3=yv z)mED!6H2>+u;Zxto<8<9Xt45@(?|y8300?P{epb5UOjZ0LTRvgAN0Gwa^aTvL}hcM z)9UMLmZ(pjz3255xlAiZd9kwXYEhk{s~v3B#SbqriecXENeu4c8U6V)#W*Wz7YpRb zLVC&9vT1wI*EHG3Ct7ZF#!Rq;MAkc;sIb$ya+2Ek5XwAq3L;suewr*%>z-P4&q}Ax zsEye_4g<-Mn9J*lN*ADRlVX_0btd~eEGu;uXcR_)`tC!^^j(VFwKJ-BR~mm~ZhU>= zTNr;_cxu*T!7VxB&C=LX(l&Z7ZKcou4OcsR9#@%L%ez_E-QhOB!gpU@uEl@cTj}=6 zb4utCT-AslKou@|scXX_;#vr13|;2cdxC|i810%0mPcpmHEW+^gai|EgUzEXEtq-2 zG@*;-2-P_$0p0UO1rm9p^wUi_;7Y?p7k{1K!t4@hxiGazC{z9&y8B&~{Soo0+aYe{ z$lVfa8?;-5&uRUxp?d4ZgPt(ISdo%bOB#jOF+-ujYKA}jn`5q`)J-P4USjFJZw%MhK;@VL4a^|*giXyGFs{+74$5gu-fsXz^`$=Lh literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/ceremony/yd.png b/adminSystem/src/assets/images/ceremony/yd.png new file mode 100644 index 0000000000000000000000000000000000000000..426912d581d699afa00e95ad5e9c54d9cdda03f7 GIT binary patch literal 4629 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 literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/draw/draw1.png b/adminSystem/src/assets/images/draw/draw1.png new file mode 100644 index 0000000000000000000000000000000000000000..da5d87a3528ce62da611614d3cf3ed57c8b0288f GIT binary patch literal 11315 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 Q{0ji6DrrHR6s@EF2ReyiTmS$7 literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/favicon.ico b/adminSystem/src/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..21e7063503b8c6eaaad646f6592ff0372aa0cf88 GIT binary patch literal 4286 zcmeH}S!`5Q7{^ZmX^4%bgOEf`9AQ5&B8oFA?$zA!I!YZ;>n%y{{bj?D_FL0{^85fX7IpmzXuNGUKjz zsqSE+ROd>RXb=$jMCCKi_Y1J^o>{Yy4P-D{>;kHF0vQD^a=K zC3Trya<9cDQT3sGR+kj8y2POE06$o;DK@zOg=nei?L=vA2-lZUFs&RMqsnuD(}d42 zC&ek}97tT+4$(7N5IMFPBF8tw#E+Zd$;mAcJDUl7LoPs{8RUMH&jND53Ubg2Fvx&3 zWPivHz!u&0FStoJG<7Wz)l+9O7X^JU$ z^a_tMg&IAGwT7g;Mx37u(Njt9lem{e#Ws*J{(y0Z0aS zw!$z(J!)PRE_$j;`cjxGw+4AslM zEK$F>zTAjvqQX5iqpl#k1=X6A&}fWG-DXb7yXt)^_Hx+qNEpe_5=Zj1*HFC5NMdfw zMy^Ufs?`?eTlFr~VxFhilES7;RErUy71fTqifoj0)y(sqj?YBDty;#KSL$v6<4$WH z^Dcy)-;IS1ElPxoHU7AF7lCtY2y9?8`()N);2Ps1N*xZ z)nx{;i)oE@7Ovp^J2px|=1(Q>*zd`HbNs@N6sB_{`5Idt#N0OW#N&LKa~pb+Y#r8F zvc9D{WKuSbH}&0$bwxdQAiERQjWvGF3{sDIjO%f36?$ysK~o8lf{rib0Y;{EFAv+| ze0azEaNSHE4o-Vl`Qh^)5#D1U@oGko=f@)Um&TvUxkPh~Y*}F=AK5bPZ~BhuMelvK zN8U2|wLS7SM@_89dp zp?9U$sP{HhN0JSc-Gh5Tw$yLE&iV4wdEX9Yxk%3*f?OgD=DjC|?A2np;7wt8y5>~o z+%xNCNWyD>jjuKRmaIA2V>Iu`#d@RO>IZSTcIifvVXz-vipMW=;uwLsvvChcSK9^{((VC;)X`OeeF~>8$-=uz~hV>V} z$oqF{%SJBf$XJPC+2mz?j^H&M7Mjh@L@TqBZ!u|e78*oron)eR$8w?G+@N@;{3-9W z=9HXc+@TNq&1bpC`SRWp{B~`*`=w6u$N4>rqZf5wa}_z0Vx122W)OrfOI575C@tPo zsLfqyz&Dx*+sI^yO~${Ibw|A?W3Sd5{;tZR-~QEKzn)Fu2X|{rO_W&)>Q(%IVN~$s zV*K=2tN7i1OPmilbwXfzuiBU93?>>2Ntfqz3g2uwDygx@y3@SV-*i6{5C#fYi379y zIa+Qa(Q@-U`1>4@>d|{U_G>T3EocET!ZtIj#TL_WvhI*idky0* z`^-`FVwHS)^4Ra1wLr|n;CtP+lJ^>Y@)cxvW95rh zk=t?!*T&>YS4~l+?r6WEchP6PFcH|5IvIF6m-icX6KLN`;*Iu9vE9;%{drdk+J@PI zyj^#FA1)dX?wLV(_At>|NTMCZOT`XLi4uF->ptu6@qWj8*0*pR$6EIinAkuP-9^jA zK1VtqELu6{Kb$hrxBuC|d7et$_#WwUtQNbipNqY=W4zC~1%QypzE9_w{ruMR2>gE{ z0P`G2=ihz`-ltmJiy9>)b(Abgi;$(Lw6!c*yH+IWX(Gu;l@%RH1tO-xQ8^X9gf#HY I1HxPX0yQi+Qvd(} literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/lock/bg_dark.webp b/adminSystem/src/assets/images/lock/bg_dark.webp new file mode 100644 index 0000000000000000000000000000000000000000..1c334354f0145e7ae1d91c8d51022fb8067d0b83 GIT binary patch literal 70592 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

TD-eWE$*|X9h61tDog6 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 ZSO66fIy%JW+RF@^AI^XP00000008fkeAui8$xP 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 literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/login/lf_icon2.webp b/adminSystem/src/assets/images/login/lf_icon2.webp new file mode 100644 index 0000000000000000000000000000000000000000..5e4f3fd0dd461d4e92ab3222aa9f546e2c4ca2bd GIT binary patch literal 25016 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 Ef{6$HX8-^I literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/settings/menu_layouts/horizontal.png b/adminSystem/src/assets/images/settings/menu_layouts/horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..ca779bc78ad5bf45e4b26e4e75e6481295383369 GIT binary patch literal 409 zcmV;K0cQS*P) zvbn(=z`lWa1n(axjo^q~;4t&bS@^%KH`vFc7`p2Q0I=EJ`>gf&x{iCB)gIPy+H$?# zI&Rh@j<_p{ds>?h{E0Z?i2HPk&-@4Ch&w}E!NafkSzG;R2LRxg$z)yzam_;Ieh_DF zDlv-FwX;E-%i6gQ+qsC>eh{Sfe@zd^TDtroDA!A5hii^ozT^kui2GyQ`9Fk7vFhZSNmiXsv;oD2I1{Wo zId{pb(`jB}R@*LWXIIUn~YTl0Pqhc#kP}ccEGly z+kj$coC&s_oYRhWw4>dQcKbJ=%iasfw_w`=03i1PMtw+J%=}$J00000NkvXXu0mjf DJf+bZ literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/settings/menu_layouts/mixed.png b/adminSystem/src/assets/images/settings/menu_layouts/mixed.png new file mode 100644 index 0000000000000000000000000000000000000000..c82b58038fcbdab260ef4df85cd0bf53fe232ecf GIT binary patch literal 431 zcmV;g0Z{&lP)Yy$(XK&WqF=)bm5 z&;o+?zlB7C)K}fi`)JgA{>Jxlc!KXfgb6|jAx2Z&sveg*c(ShsoP#IVp>AiIo`88@ zr(tz#nx24pU$**en#2z4P8E+W)>TaYs*4?@TZnE@z)$QW={$9Pq+5t?5s4t(vUL8d zk>BD6MzrHvYjymk9q8Cd*CO3GT_5SB%UpMi1j=u9n@Hh+b*4E&dq@{atWMW0ODCOl z4eTM7=ZGc+P_FT6@YfY7)!;zX6{(>H2c9mqfCEieq?Y>qVa~er;Qc|m&m&qoqK^6@ zVBN$3Du2{%NO?+K9&mf!tI@d6TsrBbTaC_bh#7oGv|&;J<@f7bI_ad#=vP1p(n&`P zAWTV(SImLAJYY(!i%iy}>sF+bPPzt{A(q{UW(H7(@yY~TT`{;`%(2Y&+R=+kRYC}n ZKX+7z8kqwQRWJYm002ovPDHLkV1jqY&3ym> literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/settings/menu_layouts/vertical.png b/adminSystem/src/assets/images/settings/menu_layouts/vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..16e942b0e9f3919eb199134692e32004544b280c GIT binary patch literal 439 zcmV;o0Z9IdP)(# zD2kaKe_n4u2iGeYpx=`+HyKd(2SbJL9}%}U{@8`CedQuyi89i?#7jp@$(zBsZj{qeqluEicf z*6Rj#SRgdoBZzCSqT|}D=pbE-bUD@21=xhdeC5x5a>aJJ;D*%S|aFn{?|8j z&_So{5lo1xtA1;g<9us`n+K#rx>Kg(1@+SDdjvb0&iU3z)8W<#y8VnK9qURScO3WW h%8Q3N0002Cig literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/settings/menu_styles/dark.png b/adminSystem/src/assets/images/settings/menu_styles/dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e1653b7a657edd6e2e83b8b479bb1b4af4620e15 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^cYt^j3p0qNwsRej;t%i%asBk^tGc?@&!0c-?cF|q z{-!dsl@Ta)!PCVtB;(%On+JK17;v}*%3nHAmJs>!0B^+Y`oPQsN=8b26YP2SNGmV+ z(<1Wuz(Ft&-I2P^pJ{r#^(-^z!+)>dS^035-TET1?9HMwt^CXP@rda`@oSCvV)pRD9`Ic+V>RN|cZ9-Q}41PonSK>R4CZA96Tw@1diy fo(~{Er;M1%fByXW@#Dw4 zckfEI`&$F0PIK_}Tq&4}k`{J&n3>JYDATM6ZXdH1D1|rwka( a+00#Z%j6q-R9HLER}7x6elF{r5}E*&a(=A< literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/settings/menu_styles/light.png b/adminSystem/src/assets/images/settings/menu_styles/light.png new file mode 100644 index 0000000000000000000000000000000000000000..3007b99ea227f5f203ad309d28358d95aa40aad2 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^cYt^j3p0qNwsRej5(@AMasBk^>;M1%fByXW@#Dw4 zckhOVhNk`~V+6{c^K@|x$+-9SW+HE^0gvlLR`&CZGg~7o#6IM+GMaerh)K|XF7e~v z*2wU>xm~svV6cO!eAexM#~%n4hWb7dn;9*-?$zE?M-Ku)rRC!eF^lI+S&sX@aO&C)xGbBzUl*1H|~cH`T*7S|L8+xoqd^+pNbuX$T2rdEOtd33n=<}rH(r4Bw?X0 z19j9Zq$jx5yQq&=Rv*u@&uV zZv5wD2VJi=A#|57GsIK59E1=t&(Kn59-)r9I&_O8$G{R>i|A%Y2O}3yS41Z&E$H;i qY~iWsL1-c78CvShBh*n>SoaHFuY$So!<9_{0000$3b*O46wRy83EweL3E-p zTDPTC^?=rG?@;xC)#cYaJXSqGb<2^89-z9fBYo%rs(T&jp$Dk$Kk9+HS#|cPcrJon zbI(i6T+zM{&>wNHId#;{s7qL=Yd{@!)cq1mSEY~blI|(J>{oO}dReaMR_P^Q(MdlO z>i~~A@u&cbFmcTrMB15WsH1KUUF~df!d~+osw?&ewtya>4AqH=XPy0+o=3$lg6Eny zh_o}$P)FSyx+-_rpi9hVN3Lj_1$lsMGW5LKxX|5xOc$eay9h37-XPM>JVPCIbLeVE zkAX{UAEGNAI~aL@@(`VV>$4IoA-;gkJ&1+Cgm8pmlOdPn{^_RrAUFTa z?Nwrlv;qeX95`^`z<~n?4ji~TxhJFJb(>akZP2?200000NkvXXu0mjfdG+rO literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/svg/403.svg b/adminSystem/src/assets/images/svg/403.svg new file mode 100644 index 0000000..68790ad --- /dev/null +++ b/adminSystem/src/assets/images/svg/403.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/adminSystem/src/assets/images/svg/404.svg b/adminSystem/src/assets/images/svg/404.svg new file mode 100644 index 0000000..48e1ca3 --- /dev/null +++ b/adminSystem/src/assets/images/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/adminSystem/src/assets/images/svg/500.svg b/adminSystem/src/assets/images/svg/500.svg new file mode 100644 index 0000000..512429f --- /dev/null +++ b/adminSystem/src/assets/images/svg/500.svg @@ -0,0 +1,5 @@ + diff --git a/adminSystem/src/assets/images/svg/login_icon.svg b/adminSystem/src/assets/images/svg/login_icon.svg new file mode 100644 index 0000000..4beb3ab --- /dev/null +++ b/adminSystem/src/assets/images/svg/login_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/adminSystem/src/assets/images/user/avatar.webp b/adminSystem/src/assets/images/user/avatar.webp new file mode 100644 index 0000000000000000000000000000000000000000..6d7234b9eb1d4962efb9d01a4703fe4540d3ec34 GIT binary patch literal 2130 zcmV-Y2(9;0Nk&FW2mkB(|iQh=J6I zyxsF})jWZrx1zAfq`;31uOrtG^aslZ++-*oHbA=Ws=r@BH$nEcT>f}v8c<1MaH|2|TsFVAnjLEj8 z)v3~&1Tg$gPbv>1OlgM(i_=4U(n13#54;Dp%@xYc7c42<_;HI{&blw#e zNxIkz>nHOh3a&_eJ&|2SelyaGrVT5H-&xr3qchs0k*P(yrJZP27PwoGyrzP(HQNq) zdnp%e(8+0Kvd=#P@!j@e@d-;H6ynU*1u2;INSHcM)CHM>4FQx&W>lUOt}HM!x%Fka z%X5GL{_uBtT#(I|h-+J>-VdN=0k`87mNLbrC!e0hz)eum&xzb_(qj;)6C_`RprQp+gHMV1x!s=;2Rb%2PsYfg* zh??Ve&~jKa^pwSDNm#|Es3PLmb7sTuZ(x2P^RA`Y&UPQ||EZaGfbNJ@(YlYt9%HYh zZd6LrWC~-IbvBzuEVN?AAJR!ig~P0-MlXvR7Z7bx7?0-n<*4=SW=S5*A~>8h3`T5! z`I{Rw5T_p0VkFU17Z&8dG9Tw3V-0ioX90$ba-jE=Z$T+Ka@XHj^&Ah5&3^#B4xzV6Zt7h5hW%IMfcreWr(GyA?@Q(v8ZjmLibkv4Aj2n8A)< z=`*~B`m=?dsA{+ox>qZp7zsL(cLHfKRXL9|zDz?~SSeGwu!UZyhdt^PsU~45yW9Oj z|7T$>K9wSeG)6;}llOZ5%}(}z0=hks13|B$4L31VdOF*sd@YU?jiH_PWPK9UZtNa*gq3V&9l+*yS!e&SOs`1NHt-i^1q)x3)% z>It^qg|*?RqvUXDdRScQn|t^Lu)KXY5VsWF%#?Kcx(iSN8x!^6ce+4JmX87y6y?wZZ4&b%iwh!QfdXkYoDFzcx@r36&& zo2APee^J><5DkZ2e=>DFvmAxNih)1iwKOrXu;mfGI%PS5k!rJn1B3wHU(p$^yBEHx zq~i|&7X}r0s20A5acrQR9#hvK2Xx=6c*tFM@J9UuD8ohc`RLDsu}deGD`*`c;m8QS zgCUifRD^>%<4S8#^2TMADqw+gjHZ>sulGW1(}gDKwF2hjvQB>n-jtmz#9@~4F*^x@%R}e{Y2&KCz?duu^aa>y%iPm%1zqnPR{G?Y4 z48C*_3ipwVb2oo`j1WM8AYDT&ioo^DN+XA-8Nq=KolVDJ>^(q9Nykz^AO8@jU-TDL8=@Yp# z`0V^~J;sb-xt;21tD-A@Q!b4`@x|E>D83vGJA5%XgyFaLGshP4j^0BP@nJi;8QPq} zZasCOfyHw%T^l_D156Rxq9l?k^_qF>+c(_`j%kBrqB>?FPa!(O&K%uAjw|3vex zg~6iGGvYe?pO(JYfzZ{%9l2^x{GmywWC-Ereh^T;KWV6K)&<>!={pX|l+g~?h}`RK z0g*I#Xa`GBWma{trr5d?fk)++ZmoO}fx6-Yk^Ncr>2Umkn-MFU(O?_Y9peO6vtnRF z$QVFE%8MY|Xohq7+4+t3Y-1=b`+7f0p6}a*tiScRyO=V+;Q3*I3IM{IKduC`j8lKO I28@6J0QOxWo&W#< literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/images/user/bg.webp b/adminSystem/src/assets/images/user/bg.webp new file mode 100644 index 0000000000000000000000000000000000000000..762b22dbbe34305886a78b1d103dddcc61d106ec GIT binary patch literal 12352 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 literal 0 HcmV?d00001 diff --git a/adminSystem/src/assets/styles/core/app.scss b/adminSystem/src/assets/styles/core/app.scss new file mode 100644 index 0000000..c0efeed --- /dev/null +++ b/adminSystem/src/assets/styles/core/app.scss @@ -0,0 +1,292 @@ +// 全局样式 +// 顶部进度条颜色 +#nprogress .bar { + 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; +} + +#nprogress .spinner-icon { + border-top-color: var(--theme-color) !important; + border-left-color: var(--theme-color) !important; +} + +// 处理移动端组件兼容性 +@media screen and (max-width: 640px) { + * { + 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: ; +} + +// 色弱模式 +.color-weak { + filter: invert(80%); + -webkit-filter: invert(80%); +} + +#noop { + display: none; +} + +// 语言切换选中样式 +.langDropDownStyle { + // 选中项背景颜色 + .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; + } + + &:last-child { + .el-dropdown-menu__item { + margin-bottom: 0 !important; + } + } + + .menu-txt { + min-width: 60px; + display: block; + } + + i { + font-size: 10px; + margin-left: 10px; + } + } +} + +// 盒子默认边框 +.page-content { + 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; + + --el-card-border-color: var(--default-border) !important; +} + +.art-card, +.art-card-sm, +.art-card-xs { + 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; + } + + .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-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; + } + + .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-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 + ); + } +} + +// 元素全屏 +.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; +} + +// 表格卡片 +.art-table-card { + 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; + } +} + +// 容器全高 +.art-full-height { + height: var(--art-full-height); + display: flex; + flex-direction: column; + + @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; + + &.art-badge-horizontal { + right: 0; + } + + &.art-badge-mixed { + right: 0; + } + + &.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; +} + +@keyframes breathe { + 0% { + opacity: 0.7; + transform: scale(1); + } + + 50% { + opacity: 1; + transform: scale(1.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; +} + +.art-loading-fix .el-loading-spinner { + position: static !important; + top: auto !important; + left: auto !important; + transform: none !important; +} + +// 去除移动端点击背景色 +@media screen and (max-width: 1180px) { + * { + -webkit-tap-highlight-color: transparent; + } +} diff --git a/adminSystem/src/assets/styles/core/dark.scss b/adminSystem/src/assets/styles/core/dark.scss new file mode 100644 index 0000000..c52abc3 --- /dev/null +++ b/adminSystem/src/assets/styles/core/dark.scss @@ -0,0 +1,93 @@ +/* +* 深色主题 +* 单页面移除深色主题 document.getElementsByTagName("html")[0].removeAttribute('class') +*/ + +$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}; + + // 富文本编辑器 + // 工具栏背景颜色 + --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; + } + + // 富文本编辑器 + .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 { + background-color: var(--default-box-color) !important; + } + + /* 下拉选择框 hover 样式调整 */ + .w-e-select-list ul li:hover, + /* 工具栏 hover 按钮背景颜色 */ + .w-e-bar-item button:hover { + 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] 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; + } + + .w-e-modal { + background-color: var(--art-color); + } + } +} diff --git a/adminSystem/src/assets/styles/core/el-dark.scss b/adminSystem/src/assets/styles/core/el-dark.scss new file mode 100644 index 0000000..8f81cdf --- /dev/null +++ b/adminSystem/src/assets/styles/core/el-dark.scss @@ -0,0 +1,2 @@ +// 导入暗黑主题 +@use 'element-plus/theme-chalk/src/dark/css-vars.scss' as *; diff --git a/adminSystem/src/assets/styles/core/el-light.scss b/adminSystem/src/assets/styles/core/el-light.scss new file mode 100644 index 0000000..ddf2bc5 --- /dev/null +++ b/adminSystem/src/assets/styles/core/el-light.scss @@ -0,0 +1,34 @@ +// https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss +// 自定义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' + ) +); diff --git a/adminSystem/src/assets/styles/core/el-ui.scss b/adminSystem/src/assets/styles/core/el-ui.scss new file mode 100644 index 0000000..7e8e150 --- /dev/null +++ b/adminSystem/src/assets/styles/core/el-ui.scss @@ -0,0 +1,524 @@ +// 优化 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; + + --el-component-custom-height: 36px !important; + + --el-component-size: var(--el-component-custom-height) !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; + + .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; +} + +// 日期选择器 +.el-date-range-picker { + --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-pagination 大小 +.el-pagination--default { + & { + --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; + } + } + + .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-pager li { + 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; +} + +// 优化菜单 item hover 动画(提升鼠标跟手感) +.el-sub-menu__title, +.el-menu-item { + transition: background-color 0s !important; +} + +// -------------------------------- 修改 el-size=default 组件默认高度 start -------------------------------- +// 修改 el-button 高度 +.el-button--default { + height: var(--el-component-custom-height) !important; +} + +// circle 按钮宽度优化 +.el-button--default.is-circle { + width: var(--el-component-custom-height) !important; +} + +// 修改 el-select 高度 +.el-select--default { + .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; +} +// -------------------------------- 修改 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; +} + +.el-popover { + 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; +} + +.el-dialog__header { + .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; +} + +.el-dialog.el-dialog-border { + .el-dialog__body { + // 上边框 + &::before, + // 下边框 + &::after { + content: ''; + position: absolute; + left: -16px; + width: calc(100% + 32px); + height: 1px; + background-color: var(--art-gray-300); + } + + &::before { + top: 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; + + p { + font-size: 13px; + } +} + +// 修改 el-dropdown 样式 +.el-dropdown-menu { + padding: 6px !important; + border-radius: 10px !important; + border: none !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; + } + + &: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; + + .el-popper__arrow { + display: none; + } +} + +.el-dropdown-selfdefine:focus { + outline: none !important; +} + +// 处理移动端组件兼容性 +@media screen and (max-width: 640px) { + .el-message-box, + .el-message, + .el-dialog { + width: calc(100% - 24px) !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__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; + + span { + margin-left: 0 !important; + } + } +} + +// 修改el-tag样式 +.el-tag { + font-weight: 500; + transition: all 0s !important; + + &.el-tag--default { + height: 26px !important; + } +} + +.el-checkbox-group { + &.el-table-filter__checkbox-group label.el-checkbox { + height: 17px !important; + + .el-checkbox__label { + font-weight: 400 !important; + } + } +} + +.el-radio--default { + // 优化单选按钮大小 + .el-radio__input { + .el-radio__inner { + width: 16px; + height: 16px; + + &::after { + width: 6px; + height: 6px; + } + } + } +} + +.el-checkbox { + .el-checkbox__inner { + border-radius: 2px !important; + } +} + +// 优化复选框样式 +.el-checkbox--default { + .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; + } + } + + .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; +} + +// 修改 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; + + &:hover { + background-color: var(--art-hover-color) !important; + color: var(--art-gray-900) !important; + } +} + +.el-message-box { + padding: 25px 20px !important; +} + +.el-message-box__title { + font-weight: 500 !important; +} + +.el-table__column-filter-trigger i { + color: var(--theme-color) !important; + margin: -3px 0 0 2px; +} + +// 去除 el-dropdown 鼠标放上去出现的边框 +.el-tooltip__trigger:focus-visible { + outline: unset; +} + +// ipad 表单右侧按钮优化 +@media screen and (max-width: 1180px) { + .el-table-fixed-column--right { + padding-right: 0 !important; + + .el-button { + margin: 5px 10px 5px 0 !important; + } + } +} + +.login-out-dialog { + 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 动画后宽度不自适应问题 + .el-select__selected-item { + display: inline-block; + } + } +} + +.dialog-fade-leave-active { + animation: fade-out 0.2s linear; + + .el-dialog:not(.is-draggable) { + animation: dialog-close 0.5s; + } +} + +@keyframes dialog-open { + 0% { + opacity: 0; + transform: scale(0.2); + } + + 100% { + opacity: 1; + transform: scale(1); + } +} + +@keyframes dialog-close { + 0% { + opacity: 1; + transform: scale(1); + } + + 100% { + opacity: 0; + transform: scale(0.2); + } +} + +// 遮罩层动画 +@keyframes fade-out { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +// 修改 el-select 样式 +.el-select__popper:not(.el-tree-select__popper) { + .el-select-dropdown__list { + padding: 5px !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; + } + + &: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-tree-select 样式 +.el-tree-select__popper { + .el-select-dropdown__list { + padding: 5px !important; + + .el-tree-node { + .el-tree-node__content { + height: 36px !important; + border-radius: 6px !important; + + &:hover { + background-color: var(--art-gray-200) !important; + } + } + } + } +} + +// 实现水波纹在文字下面效果 +.el-button > span { + position: relative; + z-index: 10; +} + +// 优化颜色选择器圆角 +.el-color-picker__color { + 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-tree-node__content { + border-radius: 4px; + margin-bottom: 4px; + padding: 1px 0; + + &: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; + } +} + +// 隐藏折叠菜单弹窗 hover 出现的边框 +.menu-left-popper:focus-within, +.horizontal-menu-popper:focus-within { + 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; + } +} diff --git a/adminSystem/src/assets/styles/core/md.scss b/adminSystem/src/assets/styles/core/md.scss new file mode 100644 index 0000000..b22fdc2 --- /dev/null +++ b/adminSystem/src/assets/styles/core/md.scss @@ -0,0 +1,1036 @@ +/* 文章标题设置(h1-h6)*/ +/* ------------------------------------------------ */ +$font-color: #24292e; + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + color: var(--art-gray-800) !important; + margin: 30px 0 10px 0; + font-weight: 600; +} + +.markdown-body h1 { + font-size: 30px; +} + +@media only screen and (max-width: 550px) { + .markdown-body h1 { + font-size: 26px; + } + + .markdown-body h2 { + font-size: 22px; + } + + .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; +} + +/* 详情页文章字体颜色 */ +/* ------------------------------------------------ */ +.markdown-body p { + line-height: 28px; + margin-bottom: 10px; +} + +.markdown-body li, +.markdown-body p { + 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; +} + +.dark .markdown-body p span { + 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; +} + +.line-numbers-mode pre { + 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; +} + +.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; +} + +.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; +} + +.line-numbers-mode .copy-btn div { + width: 34px; + height: 34px; + line-height: 34px; + cursor: pointer; + text-align: center; + font-size: 20px; +} + +.line-numbers-mode:hover .copy-btn { + opacity: 1; +} + +.line-numbers-mode .copy-btn span { + height: 34px; + line-height: 34px; + font-size: 13px; + padding-left: 10px; + display: none; +} + +.line-numbers-mode .copy-btn .show-copy { + opacity: 1; + display: block; +} + +.line-numbers-mode ::-webkit-scrollbar-track { + background-color: #292b30 !important; +} + +.markdown-body .anchor { + float: left; + line-height: 1; + margin-left: -20px; + padding-right: 4px; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: #1b1f23; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1:hover .anchor .octicon-link:before, +.markdown-body h2:hover .anchor .octicon-link:before, +.markdown-body h3:hover .anchor .octicon-link:before, +.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; +} + +.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; +} + +.markdown-body details { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body a { + background-color: initial; +} + +.markdown-body a:active, +.markdown-body a:hover { + outline-width: 0; +} + +.markdown-body strong { + font-weight: inherit; + font-weight: bolder; +} + +.markdown-body p br { + display: inline; + line-height: 11px; +} + +.markdown-body img { + border-style: none; +} + +.markdown-body hr { + box-sizing: initial; + height: 0; + overflow: visible; +} + +.markdown-body input { + font: inherit; + margin: 0; +} + +.markdown-body input { + overflow: visible; +} + +.markdown-body [type='checkbox'] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body * { + box-sizing: border-box; +} + +.markdown-body input { + font-size: inherit; + line-height: inherit; +} + +.markdown-body a { + color: #0366d6; + text-decoration: none; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body strong { + font-weight: 600; +} + +.markdown-body hr { + 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: ''; +} + +.markdown-body hr:after { + clear: both; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + 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; +} + +.markdown-body blockquote { + margin: 0; +} + +.markdown-body ol, +.markdown-body ul { + padding-left: 0; + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body ol ol, +.markdown-body ul ol { + 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; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body code, +.markdown-body pre, +.markdown-body .line-number { + 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 pre { + 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; +} + +.markdown-body :checked + .radio-label { + position: relative; + z-index: 1; + border-color: #0366d6; +} + +.markdown-body .border { + border: 1px solid #e1e4e8 !important; +} + +.markdown-body .border-0 { + border: 0 !important; +} + +.markdown-body .border-bottom { + border-bottom: 1px solid #e1e4e8 !important; +} + +.markdown-body .rounded-1 { + border-radius: 3px !important; +} + +.markdown-body .bg-white { + background-color: #fff !important; +} + +.markdown-body .bg-gray-light { + background-color: #fafbfc !important; +} + +.markdown-body .text-gray-light { + color: #6a737d !important; +} + +.markdown-body .mb-0 { + margin-bottom: 0 !important; +} + +.markdown-body .my-2 { + margin-top: 8px !important; + margin-bottom: 8px !important; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .py-2 { + padding-top: 8px !important; + padding-bottom: 8px !important; +} + +.markdown-body .pl-3, +.markdown-body .px-3 { + padding-left: 16px !important; +} + +.markdown-body .px-3 { + padding-right: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body .f6 { + font-size: 12px !important; +} + +.markdown-body .lh-condensed { + line-height: 1.25 !important; +} + +.markdown-body .text-bold { + font-weight: 600 !important; +} + +.markdown-body .pl-c { + color: #6a737d; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #005cc5; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6f42c1; +} + +.markdown-body .pl-s .pl-s1, +.markdown-body .pl-smi { + color: $font-color; +} + +.markdown-body .pl-ent { + color: #22863a; +} + +.markdown-body .pl-k { + color: #d73a49; +} + +.markdown-body .pl-pds, +.markdown-body .pl-s, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sra, +.markdown-body .pl-sr .pl-sre { + color: #032f62; +} + +.markdown-body .pl-smw, +.markdown-body .pl-v { + color: #e36209; +} + +.markdown-body .pl-bu { + color: #b31d28; +} + +.markdown-body .pl-ii { + color: #fafbfc; + background-color: #b31d28; +} + +.markdown-body .pl-c2 { + color: #fafbfc; + background-color: #d73a49; +} + +.markdown-body .pl-c2:before { + content: '^M'; +} + +.markdown-body .pl-sr .pl-cce { + font-weight: 700; + color: #22863a; +} + +.markdown-body .pl-ml { + color: #735c0f; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: 700; + color: #005cc5; +} + +.markdown-body .pl-mi { + font-style: italic; + color: $font-color; +} + +.markdown-body .pl-mb { + font-weight: 700; + color: $font-color; +} + +.markdown-body .pl-md { + color: #b31d28; + background-color: #ffeef0; +} + +.markdown-body .pl-mi1 { + color: #22863a; + background-color: #f0fff4; +} + +.markdown-body .pl-mc { + color: #e36209; + background-color: #ffebda; +} + +.markdown-body .pl-mi2 { + color: #f6f8fa; + background-color: #005cc5; +} + +.markdown-body .pl-mdr { + font-weight: 700; + color: #6f42c1; +} + +.markdown-body .pl-ba { + color: #586069; +} + +.markdown-body .pl-sg { + color: #959da5; +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: #032f62; +} + +.markdown-body .mb-0 { + margin-bottom: 0 !important; +} + +.markdown-body .my-2 { + margin-bottom: 8px !important; +} + +.markdown-body .my-2 { + margin-top: 8px !important; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .py-2 { + padding-top: 8px !important; + padding-bottom: 8px !important; +} + +.markdown-body .pl-3 { + padding-left: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body .pl-7 { + padding-left: 48px !important; +} + +.markdown-body .pl-8 { + padding-left: 64px !important; +} + +.markdown-body .pl-9 { + padding-left: 80px !important; +} + +.markdown-body .pl-10 { + padding-left: 96px !important; +} + +.markdown-body .pl-11 { + padding-left: 112px !important; +} + +.markdown-body .pl-12 { + padding-left: 128px !important; +} + +.markdown-body hr { + 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; +} + +.markdown-body:after, +.markdown-body:before { + display: table; + content: ''; +} + +.markdown-body:after { + clear: both; +} + +.markdown-body > :first-child { + margin-top: 0 !important; +} + +.markdown-body > :last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body blockquote, +.markdown-body details, +.markdown-body dl, +.markdown-body ol, +.markdown-body pre, +.markdown-body table, +.markdown-body ul { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body ol, +.markdown-body ul { + 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; +} + +.markdown-body li { + line-height: 28px; + font-size: 14px; + word-wrap: break-all; + list-style: disc; + margin-left: 10px; +} + +.markdown-body li > p { + margin-top: 16px; +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body table { + display: block; + width: 100%; + overflow: auto; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table td, +.markdown-body table th { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} + +.markdown-body table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.markdown-body table tr:nth-child(2n) { + 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; +} + +.markdown-body img[align='right'] { + padding-left: 20px; +} + +.markdown-body img[align='left'] { + 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; +} + +.markdown-body pre { + word-wrap: normal; +} + +.markdown-body pre > code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + 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; +} + +.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; +} + +.markdown-body .commit-tease-sha { + display: inline-block; + font-size: 90%; + color: #444d56; +} + +.markdown-body .full-commit .btn-outline:not(:disabled):hover { + color: #005cc5; + border-color: #005cc5; +} + +.markdown-body .blob-wrapper { + overflow-x: auto; + overflow-y: hidden; +} + +.markdown-body .blob-wrapper-embedded { + 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; +} + +.markdown-body .blob-num:hover { + color: rgba(27, 31, 35, 0.6); +} + +.markdown-body .blob-num:before { + content: attr(data-line-number); +} + +.markdown-body .blob-code { + 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; +} + +.markdown-body .pl-token.active, +.markdown-body .pl-token:hover { + cursor: pointer; + background: #ffea7f; +} + +.markdown-body .tab-size[data-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; +} + +.markdown-body .tab-size[data-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; +} + +.markdown-body .tab-size[data-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; +} + +.markdown-body .tab-size[data-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; +} + +.markdown-body .tab-size[data-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; +} + +.markdown-body .tab-size[data-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; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item + .task-list-item { + margin-top: 3px; +} + +.markdown-body .task-list-item input { + margin: 0 0.2em 0.25em -1.6em; + vertical-align: middle; +} diff --git a/adminSystem/src/assets/styles/core/mixin.scss b/adminSystem/src/assets/styles/core/mixin.scss new file mode 100644 index 0000000..db36888 --- /dev/null +++ b/adminSystem/src/assets/styles/core/mixin.scss @@ -0,0 +1,157 @@ +// sass 混合宏(函数) + +/** +* 溢出省略号 +* @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; + } +} + +/** +* 控制用户能否选中文本 +* @param {String} 类型 +*/ +@mixin userSelect($value: none) { + 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; +} + +/** +* css3动画 +* +*/ +@mixin animation( + $from: ( + width: 0px + ), + $to: ( + width: 100px + ), + $name: mymove, + $animate: mymove 2s 1 linear infinite +) { + -webkit-animation: $animate; + -o-animation: $animate; + animation: $animate; + + @keyframes #{$name} { + from { + @each $key, $value in $from { + #{$key}: #{$value}; + } + } + + to { + @each $key, $value in $to { + #{$key}: #{$value}; + } + } + } + + @-webkit-keyframes #{$name} { + from { + @each $key, $value in $from { + $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; +} + +// placeholder +@mixin placeholder($color: #bbb) { + // Firefox + &::-moz-placeholder { + color: $color; + opacity: 1; + } + + // Internet Explorer 10+ + &:-ms-input-placeholder { + color: $color; + } + + // Safari and Chrome + &::-webkit-input-placeholder { + color: $color; + } + + &: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})'; +} + +//添加浏览器前缀 +@mixin browserPrefix($propertyName, $value) { + @each $prefix in -webkit-, -moz-, -ms-, -o-, '' { + #{$prefix}#{$propertyName}: $value; + } +} + +// 边框 +@mixin border($color: red) { + 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); +} diff --git a/adminSystem/src/assets/styles/core/reset.scss b/adminSystem/src/assets/styles/core/reset.scss new file mode 100644 index 0000000..17a3bcf --- /dev/null +++ b/adminSystem/src/assets/styles/core/reset.scss @@ -0,0 +1,41 @@ +@charset "UTF-8"; + +/*滚动条*/ +/*滚动条整体部分,必须要设置*/ +::-webkit-scrollbar { + width: 8px !important; + height: 0 !important; +} + +/*滚动条的轨道*/ +::-webkit-scrollbar-track { + 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; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #b0abab !important; +} + +/*滚动条的上下两端的按钮*/ +::-webkit-scrollbar-button { + height: 0px; + width: 0; +} + +.dark { + ::-webkit-scrollbar-track { + background-color: var(--default-bg-color); + } + + ::-webkit-scrollbar-thumb { + background-color: var(--art-gray-300) !important; + } +} diff --git a/adminSystem/src/assets/styles/core/router-transition.scss b/adminSystem/src/assets/styles/core/router-transition.scss new file mode 100644 index 0000000..f47c741 --- /dev/null +++ b/adminSystem/src/assets/styles/core/router-transition.scss @@ -0,0 +1,104 @@ +@use 'sass:map'; + +// === 变量区域 === +$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) +); + +// 抽取配置值函数,提高可复用性 +@function transition-config($key) { + @return map.get($transition, $key); +} + +// 变量简写 +$duration: transition-config('duration'); +$distance: transition-config('distance'); +$easing: transition-config('easing'); +$fade-easing: transition-config('fade-easing'); + +// === 动画类 === + +// 淡入淡出动画 +.fade { + &-enter-active, + &-leave-active { + transition: opacity $duration $fade-easing; + will-change: opacity; + } + + &-enter-from, + &-leave-to { + opacity: 0; + } + + &-enter-to, + &-leave-from { + opacity: 1; + } +} + +// 滑动动画通用样式 +@mixin slide-transition($direction) { + $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; + } + + &-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; + } + + &-enter-from { + opacity: 0; + transform: translate3d($distance-x, $distance-y, 0); + } + + &-enter-to { + opacity: 1; + transform: translate3d(0, 0, 0); + } + + &-leave-to { + opacity: 0; + transform: translate3d(-$distance-x, -$distance-y, 0); + } +} + +// 滑动动画方向类 +.slide-left { + @include slide-transition('left'); +} +.slide-right { + @include slide-transition('right'); +} +.slide-top { + @include slide-transition('top'); +} +.slide-bottom { + @include slide-transition('bottom'); +} diff --git a/adminSystem/src/assets/styles/core/tailwind.css b/adminSystem/src/assets/styles/core/tailwind.css new file mode 100644 index 0000000..1a9e22c --- /dev/null +++ b/adminSystem/src/assets/styles/core/tailwind.css @@ -0,0 +1,208 @@ +@import 'tailwindcss'; +@custom-variant dark (&:where(.dark, .dark *)); + +/* ==================== Light Mode Variables ==================== */ +:root { + /* 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); + + /* 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); + + --default-border: #e2e8ee; + --default-border-dashed: #dbdfe9; + + /* Background Colors */ + --default-bg-color: #fafbfc; + --default-box-color: #ffffff; + + /* Hover Color */ + --art-hover-color: #edeff0; + + /* Active Color */ + --art-active-color: #f2f4f5; + + /* Element Component Active Color */ + --art-el-active-color: #f2f4f5; +} + +/* ==================== Dark Mode Variables ==================== */ +.dark { + /* 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; + + /* Border Colors */ + --art-card-border: rgba(255, 255, 255, 0.08); + + --default-border: rgba(255, 255, 255, 0.1); + --default-border-dashed: #363843; + + /* Background Colors */ + --default-bg-color: #070707; + --default-box-color: #161618; + + /* Hover Color */ + --art-hover-color: #252530; + + /* Active Color */ + --art-active-color: #202226; + + /* 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); + + /* System Theme Color */ + --color-theme: var(--theme-color); + + /* Hover Color */ + --color-hover-color: var(--art-hover-color); + + /* Active Color */ + --color-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); + + /* 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); +} + +@utility rounded-custom-sm { + border-radius: calc(var(--custom-radius) / 2 + 2px); +} + +/* ==================== Custom Utility Classes ==================== */ +@layer utilities { + /* Flexbox Layout Utilities */ + .flex-c { + @apply flex items-center; + } + + .flex-b { + @apply flex justify-between; + } + + .flex-cc { + @apply flex items-center justify-center; + } + + .flex-cb { + @apply flex items-center justify-between; + } + + /* Transition Utilities */ + .tad-200 { + @apply transition-all duration-200; + } + + .tad-300 { + @apply transition-all duration-300; + } + + /* Border Utilities */ + .border-full-d { + @apply border 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-l-d { + @apply border-l border-[var(--default-border)]; + } + + .border-r-d { + @apply border-r border-[var(--default-border)]; + } + + /* 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; + + .title { + h4 { + @apply text-lg font-medium text-g-900; + } + + p { + @apply mt-1 text-sm text-g-600; + + span { + @apply ml-2 font-medium; + } + } + } + } +} diff --git a/adminSystem/src/assets/styles/core/theme-animation.scss b/adminSystem/src/assets/styles/core/theme-animation.scss new file mode 100644 index 0000000..377b945 --- /dev/null +++ b/adminSystem/src/assets/styles/core/theme-animation.scss @@ -0,0 +1,63 @@ +// 定义基础变量 +$bg-animation-color-light: #000; +$bg-animation-color-dark: #fff; +$bg-animation-duration: 0.5s; + +html { + --bg-animation-color: $bg-animation-color-light; + + &.dark { + --bg-animation-color: $bg-animation-color-dark; + } + + // View transition styles + &::view-transition-old(*) { + animation: none; + } + + &::view-transition-new(*) { + animation: clip $bg-animation-duration ease-in both; + } + + &::view-transition-old(root) { + z-index: 1; + } + + &::view-transition-new(root) { + z-index: 9999; + } + + &.dark { + &::view-transition-old(*) { + animation: clip $bg-animation-duration ease-in reverse both; + } + + &::view-transition-new(*) { + animation: none; + } + + &::view-transition-old(root) { + z-index: 9999; + } + + &::view-transition-new(root) { + z-index: 1; + } + } +} + +// 定义动画 +@keyframes clip { + from { + clip-path: circle(0% at var(--x) var(--y)); + } + + to { + clip-path: circle(var(--r) at var(--x) var(--y)); + } +} + +// body 相关样式 +body { + background-color: var(--bg-animation-color); +} diff --git a/adminSystem/src/assets/styles/core/theme-change.scss b/adminSystem/src/assets/styles/core/theme-change.scss new file mode 100644 index 0000000..5b640d2 --- /dev/null +++ b/adminSystem/src/assets/styles/core/theme-change.scss @@ -0,0 +1,11 @@ +// 主题切换过渡优化,优化除视觉上的不适感 +.theme-change { + * { + transition: 0s !important; + } + + .el-switch__core, + .el-switch__action { + transition: all 0.3s !important; + } +} diff --git a/adminSystem/src/assets/styles/custom/one-dark-pro.scss b/adminSystem/src/assets/styles/custom/one-dark-pro.scss new file mode 100644 index 0000000..36bdf63 --- /dev/null +++ b/adminSystem/src/assets/styles/custom/one-dark-pro.scss @@ -0,0 +1,98 @@ +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + + color: #a6accd; +} + +.hljs-string, +.hljs-section, +.hljs-selector-class, +.hljs-template-variable, +.hljs-deletion { + color: #aed07e !important; +} + +.hljs-comment, +.hljs-quote { + color: #6f747d; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #c792ea; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #c86068; +} + +.hljs-literal { + color: #56b6c2; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #abb2bf; +} + +.hljs-attribute { + color: #c792ea; +} + +.hljs-function { + color: #c792ea; +} + +.hljs-type { + color: #f07178; +} + +.hljs-title { + color: #82aaff !important; +} + +.hljs-built_in, +.hljs-class { + color: #82aaff; +} + +// 括号 +.hljs-params { + color: #a6accd; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #de7e61; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id { + color: #61aeee; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/adminSystem/src/assets/styles/index.scss b/adminSystem/src/assets/styles/index.scss new file mode 100644 index 0000000..cdc2ddc --- /dev/null +++ b/adminSystem/src/assets/styles/index.scss @@ -0,0 +1,23 @@ +// 重置默认样式 +@use './core/reset.scss'; + +// 应用全局样式 +@use './core/app.scss'; + +// Element Plus 样式优化 +@use './core/el-ui.scss'; + +// Element Plus 暗黑主题 +@use './core/el-dark.scss'; + +// 暗黑主题样式优化 +@use './core/dark.scss'; + +// 路由切换动画 +@use './core/router-transition'; + +// 主题切换过渡优化 +@use './core/theme-change.scss'; + +// 主题切换圆形扩散动画 +@use './core/theme-animation.scss'; diff --git a/adminSystem/src/assets/svg/loading.ts b/adminSystem/src/assets/svg/loading.ts new file mode 100644 index 0000000..fdfb078 --- /dev/null +++ b/adminSystem/src/assets/svg/loading.ts @@ -0,0 +1,32 @@ +// 自定义四点旋转SVG +export const fourDotsSpinnerSvg = ` + + + + + + + + + +` diff --git a/adminSystem/src/components/core/banners/art-basic-banner/index.vue b/adminSystem/src/components/core/banners/art-basic-banner/index.vue new file mode 100644 index 0000000..65b47e4 --- /dev/null +++ b/adminSystem/src/components/core/banners/art-basic-banner/index.vue @@ -0,0 +1,343 @@ + + + + + + diff --git a/adminSystem/src/components/core/banners/art-card-banner/index.vue b/adminSystem/src/components/core/banners/art-card-banner/index.vue new file mode 100644 index 0000000..8a5f9d4 --- /dev/null +++ b/adminSystem/src/components/core/banners/art-card-banner/index.vue @@ -0,0 +1,114 @@ + + + + diff --git a/adminSystem/src/components/core/base/art-back-to-top/index.vue b/adminSystem/src/components/core/base/art-back-to-top/index.vue new file mode 100644 index 0000000..6f8da61 --- /dev/null +++ b/adminSystem/src/components/core/base/art-back-to-top/index.vue @@ -0,0 +1,40 @@ + + + + diff --git a/adminSystem/src/components/core/base/art-logo/index.vue b/adminSystem/src/components/core/base/art-logo/index.vue new file mode 100644 index 0000000..8bc8309 --- /dev/null +++ b/adminSystem/src/components/core/base/art-logo/index.vue @@ -0,0 +1,21 @@ + + + + diff --git a/adminSystem/src/components/core/base/art-svg-icon/index.vue b/adminSystem/src/components/core/base/art-svg-icon/index.vue new file mode 100644 index 0000000..0bfcd0c --- /dev/null +++ b/adminSystem/src/components/core/base/art-svg-icon/index.vue @@ -0,0 +1,24 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-bar-chart-card/index.vue b/adminSystem/src/components/core/cards/art-bar-chart-card/index.vue new file mode 100644 index 0000000..6815c2b --- /dev/null +++ b/adminSystem/src/components/core/cards/art-bar-chart-card/index.vue @@ -0,0 +1,103 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-data-list-card/index.vue b/adminSystem/src/components/core/cards/art-data-list-card/index.vue new file mode 100644 index 0000000..fc43323 --- /dev/null +++ b/adminSystem/src/components/core/cards/art-data-list-card/index.vue @@ -0,0 +1,74 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-donut-chart-card/index.vue b/adminSystem/src/components/core/cards/art-donut-chart-card/index.vue new file mode 100644 index 0000000..df2dcbb --- /dev/null +++ b/adminSystem/src/components/core/cards/art-donut-chart-card/index.vue @@ -0,0 +1,124 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-image-card/index.vue b/adminSystem/src/components/core/cards/art-image-card/index.vue new file mode 100644 index 0000000..d27fe00 --- /dev/null +++ b/adminSystem/src/components/core/cards/art-image-card/index.vue @@ -0,0 +1,89 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-line-chart-card/index.vue b/adminSystem/src/components/core/cards/art-line-chart-card/index.vue new file mode 100644 index 0000000..e58c9b2 --- /dev/null +++ b/adminSystem/src/components/core/cards/art-line-chart-card/index.vue @@ -0,0 +1,126 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-progress-card/index.vue b/adminSystem/src/components/core/cards/art-progress-card/index.vue new file mode 100644 index 0000000..048a836 --- /dev/null +++ b/adminSystem/src/components/core/cards/art-progress-card/index.vue @@ -0,0 +1,86 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-stats-card/index.vue b/adminSystem/src/components/core/cards/art-stats-card/index.vue new file mode 100644 index 0000000..8e0341b --- /dev/null +++ b/adminSystem/src/components/core/cards/art-stats-card/index.vue @@ -0,0 +1,67 @@ + + + + diff --git a/adminSystem/src/components/core/cards/art-timeline-list-card/index.vue b/adminSystem/src/components/core/cards/art-timeline-list-card/index.vue new file mode 100644 index 0000000..fbb2c78 --- /dev/null +++ b/adminSystem/src/components/core/cards/art-timeline-list-card/index.vue @@ -0,0 +1,69 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-bar-chart/index.vue b/adminSystem/src/components/core/charts/art-bar-chart/index.vue new file mode 100644 index 0000000..d677196 --- /dev/null +++ b/adminSystem/src/components/core/charts/art-bar-chart/index.vue @@ -0,0 +1,203 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-dual-bar-compare-chart/index.vue b/adminSystem/src/components/core/charts/art-dual-bar-compare-chart/index.vue new file mode 100644 index 0000000..32aa60f --- /dev/null +++ b/adminSystem/src/components/core/charts/art-dual-bar-compare-chart/index.vue @@ -0,0 +1,195 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-h-bar-chart/index.vue b/adminSystem/src/components/core/charts/art-h-bar-chart/index.vue new file mode 100644 index 0000000..2e34759 --- /dev/null +++ b/adminSystem/src/components/core/charts/art-h-bar-chart/index.vue @@ -0,0 +1,208 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-k-line-chart/index.vue b/adminSystem/src/components/core/charts/art-k-line-chart/index.vue new file mode 100644 index 0000000..0061b51 --- /dev/null +++ b/adminSystem/src/components/core/charts/art-k-line-chart/index.vue @@ -0,0 +1,152 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-line-chart/index.vue b/adminSystem/src/components/core/charts/art-line-chart/index.vue new file mode 100644 index 0000000..b70c2c3 --- /dev/null +++ b/adminSystem/src/components/core/charts/art-line-chart/index.vue @@ -0,0 +1,371 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-radar-chart/index.vue b/adminSystem/src/components/core/charts/art-radar-chart/index.vue new file mode 100644 index 0000000..e99fff6 --- /dev/null +++ b/adminSystem/src/components/core/charts/art-radar-chart/index.vue @@ -0,0 +1,105 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-ring-chart/index.vue b/adminSystem/src/components/core/charts/art-ring-chart/index.vue new file mode 100644 index 0000000..79115f7 --- /dev/null +++ b/adminSystem/src/components/core/charts/art-ring-chart/index.vue @@ -0,0 +1,133 @@ + + + + diff --git a/adminSystem/src/components/core/charts/art-scatter-chart/index.vue b/adminSystem/src/components/core/charts/art-scatter-chart/index.vue new file mode 100644 index 0000000..995b56a --- /dev/null +++ b/adminSystem/src/components/core/charts/art-scatter-chart/index.vue @@ -0,0 +1,115 @@ + + + + diff --git a/adminSystem/src/components/core/forms/art-button-more/index.vue b/adminSystem/src/components/core/forms/art-button-more/index.vue new file mode 100644 index 0000000..858d305 --- /dev/null +++ b/adminSystem/src/components/core/forms/art-button-more/index.vue @@ -0,0 +1,71 @@ + + + + diff --git a/adminSystem/src/components/core/forms/art-button-table/index.vue b/adminSystem/src/components/core/forms/art-button-table/index.vue new file mode 100644 index 0000000..5e2fd56 --- /dev/null +++ b/adminSystem/src/components/core/forms/art-button-table/index.vue @@ -0,0 +1,59 @@ + + + + diff --git a/adminSystem/src/components/core/forms/art-drag-verify/index.vue b/adminSystem/src/components/core/forms/art-drag-verify/index.vue new file mode 100644 index 0000000..5306e04 --- /dev/null +++ b/adminSystem/src/components/core/forms/art-drag-verify/index.vue @@ -0,0 +1,430 @@ + + + + + + + + diff --git a/adminSystem/src/components/core/forms/art-excel-export/index.vue b/adminSystem/src/components/core/forms/art-excel-export/index.vue new file mode 100644 index 0000000..08207c2 --- /dev/null +++ b/adminSystem/src/components/core/forms/art-excel-export/index.vue @@ -0,0 +1,389 @@ + + + + + + diff --git a/adminSystem/src/components/core/forms/art-excel-import/index.vue b/adminSystem/src/components/core/forms/art-excel-import/index.vue new file mode 100644 index 0000000..8aa82fe --- /dev/null +++ b/adminSystem/src/components/core/forms/art-excel-import/index.vue @@ -0,0 +1,62 @@ + + + + diff --git a/adminSystem/src/components/core/forms/art-form/index.vue b/adminSystem/src/components/core/forms/art-form/index.vue new file mode 100644 index 0000000..1e76f14 --- /dev/null +++ b/adminSystem/src/components/core/forms/art-form/index.vue @@ -0,0 +1,311 @@ + + + + + + diff --git a/adminSystem/src/components/core/forms/art-search-bar/index.vue b/adminSystem/src/components/core/forms/art-search-bar/index.vue new file mode 100644 index 0000000..b25b5bb --- /dev/null +++ b/adminSystem/src/components/core/forms/art-search-bar/index.vue @@ -0,0 +1,437 @@ + + + + + + + + diff --git a/adminSystem/src/components/core/forms/art-wang-editor/index.vue b/adminSystem/src/components/core/forms/art-wang-editor/index.vue new file mode 100644 index 0000000..cfc457e --- /dev/null +++ b/adminSystem/src/components/core/forms/art-wang-editor/index.vue @@ -0,0 +1,219 @@ + + + + + + diff --git a/adminSystem/src/components/core/forms/art-wang-editor/style.scss b/adminSystem/src/components/core/forms/art-wang-editor/style.scss new file mode 100644 index 0000000..fd5dbca --- /dev/null +++ b/adminSystem/src/components/core/forms/art-wang-editor/style.scss @@ -0,0 +1,210 @@ +$box-radius: calc(var(--custom-radius) / 3 + 2px); + +// 全屏容器 z-index 调整 +.w-e-full-screen-container { + z-index: 100 !important; +} + +/* 编辑器容器 */ +.editor-wrapper { + 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; + } + + .menu-item { + display: flex; + flex-direction: row; + align-items: center; + + i { + margin-right: 5px; + } + } + + /* 工具栏 */ + .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 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; + } + + /* 下拉选择框 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); + + /* toolbar 图标和文字颜色 */ + --w-e-toolbar-color: #000; + + /* 表格选中时候的边框颜色 */ + --w-e-textarea-selected-border-color: #ddd; + + /* 表格头背景颜色 */ + --w-e-textarea-slight-bg-color: var(--art-gray-200); + } + + /* 工具栏按钮样式 */ + .w-e-bar-item svg { + fill: var(--art-gray-800); + } + + .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); + } + + /* 工具栏分割线 */ + .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 { + 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-drop-panel { + border: 0; + border-radius: $box-radius; + } + + a { + color: #318ef4; + } + + .w-e-text-container { + strong, + b { + font-weight: 500; + } + + 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: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); + } + + /* 输入区域弹出 bar */ + .w-e-hover-bar { + 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; + + &:hover { + border: 0; + } + + img { + border: 1px solid transparent; + transition: border 0.3s; + + &: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; + } + + .left-top { + top: -6px; + left: -6px; + } + + .right-top { + top: -6px; + right: -6px; + } + + .left-bottom { + bottom: -6px; + left: -6px; + } + + .right-bottom { + right: -6px; + bottom: -6px; + } + } +} diff --git a/adminSystem/src/components/core/layouts/art-breadcrumb/index.vue b/adminSystem/src/components/core/layouts/art-breadcrumb/index.vue new file mode 100644 index 0000000..4b54859 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-breadcrumb/index.vue @@ -0,0 +1,142 @@ + + + + diff --git a/adminSystem/src/components/core/layouts/art-chat-window/index.vue b/adminSystem/src/components/core/layouts/art-chat-window/index.vue new file mode 100644 index 0000000..f3d9471 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-chat-window/index.vue @@ -0,0 +1,262 @@ + + + + diff --git a/adminSystem/src/components/core/layouts/art-fast-enter/index.vue b/adminSystem/src/components/core/layouts/art-fast-enter/index.vue new file mode 100644 index 0000000..fdde222 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-fast-enter/index.vue @@ -0,0 +1,113 @@ + + + + diff --git a/adminSystem/src/components/core/layouts/art-fireworks-effect/index.vue b/adminSystem/src/components/core/layouts/art-fireworks-effect/index.vue new file mode 100644 index 0000000..be85274 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-fireworks-effect/index.vue @@ -0,0 +1,633 @@ + + + + diff --git a/adminSystem/src/components/core/layouts/art-global-component/index.vue b/adminSystem/src/components/core/layouts/art-global-component/index.vue new file mode 100644 index 0000000..6908f94 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-global-component/index.vue @@ -0,0 +1,14 @@ + + + + diff --git a/adminSystem/src/components/core/layouts/art-global-search/index.vue b/adminSystem/src/components/core/layouts/art-global-search/index.vue new file mode 100644 index 0000000..a7d88df --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-global-search/index.vue @@ -0,0 +1,426 @@ + + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-header-bar/index.vue b/adminSystem/src/components/core/layouts/art-header-bar/index.vue new file mode 100644 index 0000000..4e3c8f9 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-header-bar/index.vue @@ -0,0 +1,485 @@ + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue b/adminSystem/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue new file mode 100644 index 0000000..c8c5832 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-header-bar/widget/ArtUserMenu.vue @@ -0,0 +1,167 @@ + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue b/adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue new file mode 100644 index 0000000..edd1473 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue @@ -0,0 +1,110 @@ + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue b/adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue new file mode 100644 index 0000000..ff32c1e --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-menus/art-horizontal-menu/widget/HorizontalSubmenu.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/adminSystem/src/components/core/layouts/art-menus/art-mixed-menu/index.vue b/adminSystem/src/components/core/layouts/art-menus/art-mixed-menu/index.vue new file mode 100644 index 0000000..4e98246 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-menus/art-mixed-menu/index.vue @@ -0,0 +1,279 @@ + + + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue new file mode 100644 index 0000000..39387dc --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue @@ -0,0 +1,355 @@ + + + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss new file mode 100644 index 0000000..b98011c --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/style.scss @@ -0,0 +1,253 @@ +.layout-sidebar { + display: flex; + height: 100vh; + user-select: none; + scrollbar-width: none; + border-right: 1px solid var(--art-card-border); + + &.no-border { + border-right: none !important; + } + + // 自定义滚动条宽度 + :deep(.el-scrollbar__bar.is-vertical) { + width: 4px; + } + + :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; + + .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; + + .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; + } + + &.is-active { + background: var(--el-color-primary-light-9); + + .art-svg-icon, + span { + color: var(--theme-color) !important; + } + } + } + } + } + + .switch-btn { + position: absolute; + right: 0; + bottom: 15px; + left: 0; + margin: auto; + } + } + + .menu-left { + position: relative; + box-sizing: border-box; + height: 100vh; + + @media only screen and (width <= 640px) { + height: 100dvh; + } + + .el-menu { + height: 100%; + } + + &: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%); + + &: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; + } + } + } + + .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; + } + + 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; + } + } + } + + .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; + } + } + + .menu-model { + display: none; + } +} + +@media only screen and (width <= 800px) { + .layout-sidebar { + width: 0; + + .header { + height: 50px; + line-height: 50px; + } + + .el-menu { + height: calc(100vh - 60px); + } + + .el-menu--collapse { + width: 0; + } + + // 折叠状态下的header样式 + .menu-left-close .header { + .logo { + display: none; + } + + 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; + } + } +} + +@media only screen and (width <= 640px) { + .layout-sidebar { + border-right: 0 !important; + } +} + +.dark { + .layout-sidebar { + border-right: 1px solid rgb(255 255 255 / 13%); + + :deep(.el-scrollbar__thumb) { + background-color: #777; + } + + .dual-menu-left { + border-right: 1px solid rgb(255 255 255 / 9%) !important; + } + } +} diff --git a/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss new file mode 100644 index 0000000..7626c42 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/theme.scss @@ -0,0 +1,258 @@ +@use '@styles/core/mixin.scss' as *; + +// 菜单样式变量 +$menu-height: 42px; +$menu-icon-size: 20px; +$menu-font-size: 14px; +$hover-bg-color: var(--art-gray-200); +$popup-menu-height: 40px; +$popup-menu-padding: 8px; +$popup-menu-margin: 5px; +$popup-menu-radius: 6px; + +// 通用菜单项样式 +@mixin menu-item-base { + width: calc(100% - 16px); + margin-left: 8px; + border-radius: 6px; + + .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; + } +} + +// 通用选中样式 +@mixin menu-active($color, $bg-color, $icon-color: var(--theme-color)) { + .el-menu-item.is-active { + color: $color !important; + background-color: $bg-color; + + .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; + + .menu-icon { + margin-right: 5px; + } + + &:last-of-type { + margin-bottom: 0; + } +} + +// 主题菜单通用样式(合并 design 和 dark 主题的共同逻辑) +@mixin theme-menu-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-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; + + &.is-active { + color: $active-color !important; + background-color: $active-bg !important; + } + } + + .el-sub-menu { + @include popup-menu-item; + + height: $popup-menu-height !important; + + .el-sub-menu__title { + height: $popup-menu-height !important; + border-radius: $popup-menu-radius; + } + } + } +} + +.layout-sidebar { + // ---------------------- Modify default style ---------------------- + + // 菜单折叠样式 + .menu-left-close { + .header { + .logo { + margin: 0 auto; + } + } + } + + // 菜单图标 + .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; + + span { + font-size: $menu-font-size !important; + + @include ellipsis(); + } + } + + // 右侧箭头 + .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; + } + } + } + } + } + + // ---------------------- 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); + } + } + + // ---------------------- 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); + } + } + + // ---------------------- 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); + + .art-svg-icon { + color: var(--theme-color) !important; + } + + &::before { + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + content: ''; + background: var(--theme-color); + } + } + + @include menu-hover($hover-bg-color); + + .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; + } + + .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)); +} + +// 暗黑模式菜单样式 +.dark { + .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; + } + + // 选中的文字颜色跟图标颜色 + .el-menu-item.is-active { + span, + .menu-icon .art-svg-icon { + color: var(--theme-color) !important; + } + } + + // 右侧箭头颜色 + .el-sub-menu__icon-arrow { + color: #fff; + } + } +} diff --git a/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue new file mode 100644 index 0000000..a7ac6a9 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue @@ -0,0 +1,188 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-notification/index.vue b/adminSystem/src/components/core/layouts/art-notification/index.vue new file mode 100644 index 0000000..a58853c --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-notification/index.vue @@ -0,0 +1,456 @@ + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-page-content/index.vue b/adminSystem/src/components/core/layouts/art-page-content/index.vue new file mode 100644 index 0000000..a862df1 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-page-content/index.vue @@ -0,0 +1,136 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-screen-lock/index.vue b/adminSystem/src/components/core/layouts/art-screen-lock/index.vue new file mode 100644 index 0000000..f00199b --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-screen-lock/index.vue @@ -0,0 +1,517 @@ + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts new file mode 100644 index 0000000..35e8066 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsConfig.ts @@ -0,0 +1,248 @@ +import { computed } from 'vue' +import { useI18n } from 'vue-i18n' +import { ContainerWidthEnum } from '@/enums/appEnum' +import AppConfig from '@/config' +import { headerBarConfig } from '@/config/modules/headerBar' + +/** + * 设置项配置选项管理 + */ +export function useSettingsConfig() { + 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 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 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 configOptions = { + // 主题色彩选项 + mainColors: AppConfig.systemMainColor, + + // 主题风格选项 + themeList: AppConfig.settingThemeList, + + // 菜单布局选项 + 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配置 + } + ] + + // 根据 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) + ) + }) + + return { + // 选项配置 + tabStyleOptions, + pageTransitionOptions, + customRadiusOptions, + containerWidthOptions, + boxStyleOptions, + configOptions, + + // 设置项配置 + basicSettingsConfig + } +} diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts new file mode 100644 index 0000000..392c690 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsHandlers.ts @@ -0,0 +1,167 @@ +import { useSettingStore } from '@/store/modules/setting' +import { storeToRefs } from 'pinia' +import type { ContainerWidthEnum } from '@/enums/appEnum' + +/** + * 设置项通用处理逻辑 + */ +export function useSettingsHandlers() { + 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) + } + }, + + // 设置根元素属性 + 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) + } + } + } + + // 通用切换处理器 + 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 basicHandlers = { + // 工作台标签页 + workTab: createToggleHandler(() => settingStore.setWorkTab(!settingStore.showWorkTab)), + + // 菜单手风琴 + uniqueOpened: createToggleHandler(() => settingStore.setUniqueOpened()), + + // 显示菜单按钮 + menuButton: createToggleHandler(() => settingStore.setButton()), + + // 显示快速入口 + fastEnter: createToggleHandler(() => settingStore.setFastEnter()), + + // 显示刷新按钮 + refreshButton: createToggleHandler(() => settingStore.setShowRefreshButton()), + + // 显示面包屑 + crumbs: createToggleHandler(() => settingStore.setCrumbs()), + + // 显示语言切换 + language: createToggleHandler(() => settingStore.setLanguage()), + + // 显示进度条 + nprogress: createToggleHandler(() => settingStore.setNprogress()), + + // 色弱模式 + colorWeak: createToggleHandler( + () => settingStore.setColorWeak(), + () => { + domOperations.setHtmlClass('color-weak', settingStore.colorWeak) + } + ), + + // 水印显示 + watermark: createToggleHandler(() => + settingStore.setWatermarkVisible(!settingStore.watermarkVisible) + ), + + // 菜单展开宽度 + menuOpenWidth: createValueHandler((width: number) => + settingStore.setMenuOpenWidth(width) + ), + + // 标签页风格 + tabStyle: createValueHandler((style: string) => settingStore.setTabStyle(style)), + + // 页面切换动画 + pageTransition: createValueHandler((transition: string) => + settingStore.setPageTransition(transition) + ), + + // 圆角大小 + customRadius: createValueHandler((radius: string) => + settingStore.setCustomRadius(radius) + ) + } + + // 盒子样式处理器 + 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 + } + + setTimeout(() => { + domOperations.setRootAttribute('data-box-mode', type) + settingStore.setBorderMode() + }, 50) + } + } + + // 颜色设置处理器 + const colorHandlers = { + // 选择主题色 + selectColor: (theme: string) => { + settingStore.setElementTheme(theme) + settingStore.reload() + } + } + + // 容器设置处理器 + const containerHandlers = { + // 设置容器宽度 + setWidth: (type: ContainerWidthEnum) => { + settingStore.setContainerWidth(type) + settingStore.reload() + } + } + + return { + domOperations, + basicHandlers, + boxStyleHandlers, + colorHandlers, + containerHandlers, + createToggleHandler, + createValueHandler + } +} diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts new file mode 100644 index 0000000..66877a8 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsPanel.ts @@ -0,0 +1,192 @@ +import { ref, computed, watch } from 'vue' +import { useSettingStore } from '@/store/modules/setting' +import { storeToRefs } from 'pinia' +import { useBreakpoints } from '@vueuse/core' +import AppConfig from '@/config' +import { SystemThemeEnum, MenuTypeEnum } from '@/enums/appEnum' +import { mittBus } from '@/utils/sys' +import { useTheme } from '@/hooks/core/useTheme' +import { useCeremony } from '@/hooks/core/useCeremony' +import { useSettingsState } from './useSettingsState' +import { useSettingsHandlers } from './useSettingsHandlers' + +/** + * 设置面板核心逻辑管理 + */ +export function useSettingsPanel() { + const settingStore = useSettingStore() + const { systemThemeType, systemThemeMode, menuType } = storeToRefs(settingStore) + + // Composables + const { openFestival, cleanup } = useCeremony() + const { setSystemTheme, setSystemAutoTheme } = useTheme() + const { initColorWeak } = useSettingsState() + const { domOperations } = useSettingsHandlers() + + // 响应式状态 + const showDrawer = ref(false) + + // 使用 VueUse breakpoints 优化性能 + const breakpoints = useBreakpoints({ tablet: 1000 }) + const isMobile = breakpoints.smaller('tablet') + + // 记录窗口宽度变化前的菜单类型 + const beforeMenuType = ref() + const hasChangedMenu = ref(false) + + // 计算属性 + 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 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) + } + } + + 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 } + ) + + return { stopWatch } + } + + // 抽屉控制 + const useDrawerControl = () => { + // 打开抽屉 + const handleOpen = () => { + setTimeout(() => { + domOperations.setBodyClass('theme-change', true) + }, 500) + } + + // 关闭抽屉 + const handleClose = () => { + domOperations.setBodyClass('theme-change', false) + } + + // 打开设置 + const openSetting = () => { + showDrawer.value = true + } + + // 关闭设置 + const closeDrawer = () => { + showDrawer.value = false + } + + return { + handleOpen, + handleClose, + openSetting, + closeDrawer + } + } + + // 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 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) + + themeHandlers.initSystemTheme() + openFestival() + } + + const cleanupSettings = () => { + stopWatch() + themeCleanup?.() + cleanup() + } + + return { + initializeSettings, + cleanupSettings + } + } + + return { + // 状态 + showDrawer, + + // 方法组合 + useThemeHandlers, + useResponsiveLayout, + useDrawerControl, + usePropsWatcher, + useSettingsInitializer + } +} diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts new file mode 100644 index 0000000..65352d2 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/composables/useSettingsState.ts @@ -0,0 +1,37 @@ +import { useSettingStore } from '@/store/modules/setting' +import { MenuThemeEnum, MenuTypeEnum } from '@/enums/appEnum' + +/** + * 设置状态管理 + */ +export function useSettingsState() { + const settingStore = useSettingStore() + + // 色弱模式初始化 + 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) + } + } + + return { + // 方法 + initColorWeak, + switchMenuLayouts + } +} diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/index.vue b/adminSystem/src/components/core/layouts/art-settings-panel/index.vue new file mode 100644 index 0000000..0cbf344 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/index.vue @@ -0,0 +1,72 @@ + + + + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/style.scss b/adminSystem/src/components/core/layouts/art-settings-panel/style.scss new file mode 100644 index 0000000..e863074 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/style.scss @@ -0,0 +1,92 @@ +@use '@styles/core/mixin.scss' as *; + +// 设置抽屉模态框样式 +.setting-modal { + background: transparent !important; + + .el-drawer { + // 背景滤镜效果 + background: rgba($color: #fff, $alpha: 50%) !important; + box-shadow: 0 0 30px rgb(0 0 0 / 10%) !important; + + @include backdropBlur(); + + .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; + + .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; + } + + &.is-active { + border: 2px solid var(--theme-color); + } + + img { + width: 100%; + height: 100%; + } + } + + .name { + margin-top: 6px; + font-size: 14px; + text-align: center; + } + } + } + } + + // 去除滚动条 + .el-drawer__body::-webkit-scrollbar { + width: 0 !important; + } +} + +.dark { + .setting-modal { + .el-drawer { + background: rgba($color: #000, $alpha: 50%) !important; + + .setting-item { + .box { + border: 2px solid transparent; + } + } + } + } +} + +// 去除火狐浏览器滚动条 +:deep(.el-drawer__body) { + scrollbar-width: none; +} + +// 移动端隐藏 +@media screen and (width <= 800px) { + .mobile-hide { + display: none !important; + } +} diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue new file mode 100644 index 0000000..b6dc9d3 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/BasicSettings.vue @@ -0,0 +1,77 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue new file mode 100644 index 0000000..86c7a9e --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue @@ -0,0 +1,38 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue new file mode 100644 index 0000000..05a4b41 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/ColorSettings.vue @@ -0,0 +1,35 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue new file mode 100644 index 0000000..1f5be72 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/ContainerSettings.vue @@ -0,0 +1,33 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue new file mode 100644 index 0000000..dbcae46 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue @@ -0,0 +1,31 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue new file mode 100644 index 0000000..61237eb --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue @@ -0,0 +1,44 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue new file mode 100644 index 0000000..31ef00c --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SectionTitle.vue @@ -0,0 +1,17 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue new file mode 100644 index 0000000..7b47d1a --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingActions.vue @@ -0,0 +1,235 @@ + + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue new file mode 100644 index 0000000..85372be --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingDrawer.vue @@ -0,0 +1,51 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue new file mode 100644 index 0000000..e3ead9e --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingHeader.vue @@ -0,0 +1,18 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue new file mode 100644 index 0000000..5721027 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/SettingItem.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/adminSystem/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue b/adminSystem/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue new file mode 100644 index 0000000..4b46fcd --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-settings-panel/widget/ThemeSettings.vue @@ -0,0 +1,28 @@ + + + diff --git a/adminSystem/src/components/core/layouts/art-work-tab/index.vue b/adminSystem/src/components/core/layouts/art-work-tab/index.vue new file mode 100644 index 0000000..152ff63 --- /dev/null +++ b/adminSystem/src/components/core/layouts/art-work-tab/index.vue @@ -0,0 +1,584 @@ + + + + + + diff --git a/adminSystem/src/components/core/media/art-cutter-img/index.vue b/adminSystem/src/components/core/media/art-cutter-img/index.vue new file mode 100644 index 0000000..191ceed --- /dev/null +++ b/adminSystem/src/components/core/media/art-cutter-img/index.vue @@ -0,0 +1,350 @@ + + + + + + diff --git a/adminSystem/src/components/core/media/art-video-player/index.vue b/adminSystem/src/components/core/media/art-video-player/index.vue new file mode 100644 index 0000000..4f681ea --- /dev/null +++ b/adminSystem/src/components/core/media/art-video-player/index.vue @@ -0,0 +1,111 @@ + + + + diff --git a/adminSystem/src/components/core/others/art-menu-right/index.vue b/adminSystem/src/components/core/others/art-menu-right/index.vue new file mode 100644 index 0000000..1cc92ab --- /dev/null +++ b/adminSystem/src/components/core/others/art-menu-right/index.vue @@ -0,0 +1,415 @@ + + + + + + diff --git a/adminSystem/src/components/core/others/art-watermark/index.vue b/adminSystem/src/components/core/others/art-watermark/index.vue new file mode 100644 index 0000000..1d7f06b --- /dev/null +++ b/adminSystem/src/components/core/others/art-watermark/index.vue @@ -0,0 +1,64 @@ + + + + diff --git a/adminSystem/src/components/core/tables/art-table-header/index.vue b/adminSystem/src/components/core/tables/art-table-header/index.vue new file mode 100644 index 0000000..788c2b7 --- /dev/null +++ b/adminSystem/src/components/core/tables/art-table-header/index.vue @@ -0,0 +1,339 @@ + + + + + + diff --git a/adminSystem/src/components/core/tables/art-table/index.vue b/adminSystem/src/components/core/tables/art-table/index.vue new file mode 100644 index 0000000..2392d96 --- /dev/null +++ b/adminSystem/src/components/core/tables/art-table/index.vue @@ -0,0 +1,342 @@ + + + + + + + + + diff --git a/adminSystem/src/components/core/tables/art-table/style.scss b/adminSystem/src/components/core/tables/art-table/style.scss new file mode 100644 index 0000000..67459e8 --- /dev/null +++ b/adminSystem/src/components/core/tables/art-table/style.scss @@ -0,0 +1,99 @@ +.art-table { + position: relative; + height: 100%; + + .el-table { + height: 100%; + margin-top: 10px; + } + + :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-fade-leave-to { + opacity: 0; + } + + // 空状态垂直居中 + &.is-empty { + :deep(.el-scrollbar__wrap) { + display: flex; + } + } + + .pagination { + display: flex; + margin-top: 13px; + + :deep(.el-select) { + width: 102px !important; + } + + // 分页对齐方式 + &.left { + justify-content: flex-start; + } + + &.center { + justify-content: center; + } + + &.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; + + &: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; + + &.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); + } + } + } + } + } +} + +// 移动端分页 +@media (width <= 640px) { + :deep(.el-pagination) { + display: flex; + flex-wrap: wrap; + gap: 15px 0; + align-items: center; + justify-content: center; + } +} diff --git a/adminSystem/src/components/core/text-effect/art-count-to/index.vue b/adminSystem/src/components/core/text-effect/art-count-to/index.vue new file mode 100644 index 0000000..7fb104b --- /dev/null +++ b/adminSystem/src/components/core/text-effect/art-count-to/index.vue @@ -0,0 +1,310 @@ + + + + diff --git a/adminSystem/src/components/core/text-effect/art-festival-text-scroll/index.vue b/adminSystem/src/components/core/text-effect/art-festival-text-scroll/index.vue new file mode 100644 index 0000000..770b457 --- /dev/null +++ b/adminSystem/src/components/core/text-effect/art-festival-text-scroll/index.vue @@ -0,0 +1,32 @@ + + + + diff --git a/adminSystem/src/components/core/text-effect/art-text-scroll/index.vue b/adminSystem/src/components/core/text-effect/art-text-scroll/index.vue new file mode 100644 index 0000000..90be30f --- /dev/null +++ b/adminSystem/src/components/core/text-effect/art-text-scroll/index.vue @@ -0,0 +1,285 @@ + + + + diff --git a/adminSystem/src/components/core/theme/theme-svg/index.vue b/adminSystem/src/components/core/theme/theme-svg/index.vue new file mode 100644 index 0000000..0b565a9 --- /dev/null +++ b/adminSystem/src/components/core/theme/theme-svg/index.vue @@ -0,0 +1,100 @@ + + + + + + + diff --git a/adminSystem/src/components/core/views/exception/ArtException.vue b/adminSystem/src/components/core/views/exception/ArtException.vue new file mode 100644 index 0000000..699228f --- /dev/null +++ b/adminSystem/src/components/core/views/exception/ArtException.vue @@ -0,0 +1,43 @@ + + + diff --git a/adminSystem/src/components/core/views/login/AuthTopBar.vue b/adminSystem/src/components/core/views/login/AuthTopBar.vue new file mode 100644 index 0000000..9455253 --- /dev/null +++ b/adminSystem/src/components/core/views/login/AuthTopBar.vue @@ -0,0 +1,149 @@ + + + + + + diff --git a/adminSystem/src/components/core/views/login/LoginLeftView.vue b/adminSystem/src/components/core/views/login/LoginLeftView.vue new file mode 100644 index 0000000..af6a904 --- /dev/null +++ b/adminSystem/src/components/core/views/login/LoginLeftView.vue @@ -0,0 +1,602 @@ + + + + + + diff --git a/adminSystem/src/components/core/views/result/ArtResultPage.vue b/adminSystem/src/components/core/views/result/ArtResultPage.vue new file mode 100644 index 0000000..b2eca48 --- /dev/null +++ b/adminSystem/src/components/core/views/result/ArtResultPage.vue @@ -0,0 +1,43 @@ + + + diff --git a/adminSystem/src/components/core/widget/art-icon-button/index.vue b/adminSystem/src/components/core/widget/art-icon-button/index.vue new file mode 100644 index 0000000..760888b --- /dev/null +++ b/adminSystem/src/components/core/widget/art-icon-button/index.vue @@ -0,0 +1,23 @@ + + + + diff --git a/adminSystem/src/config/assets/images.ts b/adminSystem/src/config/assets/images.ts new file mode 100644 index 0000000..f3e89dd --- /dev/null +++ b/adminSystem/src/config/assets/images.ts @@ -0,0 +1,61 @@ +/** + * 配置图片资源 + * + * 统一管理设置中心使用的预览图片资源。 + * 包含主题样式、菜单布局、菜单风格的预览图。 + * + * ## 图片分类 + * + * - themeStyles: 系统主题预览图(亮色/暗色/自动) + * - menuLayouts: 菜单布局预览图(左侧/顶部/混合/双栏) + * - menuStyles: 菜单风格预览图(设计/暗色/亮色) + * + * @module config/assets/images + * @author Art Design Pro Team + */ + +import lightTheme from '@imgs/settings/theme_styles/light.png' +import darkTheme from '@imgs/settings/theme_styles/dark.png' +import systemTheme from '@imgs/settings/theme_styles/system.png' +import verticalLayout from '@imgs/settings/menu_layouts/vertical.png' +import horizontalLayout from '@imgs/settings/menu_layouts/horizontal.png' +import mixedLayout from '@imgs/settings/menu_layouts/mixed.png' +import dualColumnLayout from '@imgs/settings/menu_layouts/dual_column.png' +import designStyle from '@imgs/settings/menu_styles/design.png' +import darkStyle from '@imgs/settings/menu_styles/dark.png' +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 + } +} diff --git a/adminSystem/src/config/fastEnter.ts b/adminSystem/src/config/fastEnter.ts new file mode 100644 index 0000000..ccade16 --- /dev/null +++ b/adminSystem/src/config/fastEnter.ts @@ -0,0 +1,79 @@ +/** + * 快速入口配置 + * 包含:应用列表、快速链接等配置 + */ +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' + } + ] +} + +export default Object.freeze(fastEnterConfig) diff --git a/adminSystem/src/config/index.ts b/adminSystem/src/config/index.ts new file mode 100644 index 0000000..daa623a --- /dev/null +++ b/adminSystem/src/config/index.ts @@ -0,0 +1,135 @@ +/** + * 系统全局配置 + * + * 这是系统的核心配置文件,集中管理所有全局配置项。 + * 包含系统信息、主题样式、菜单布局、颜色方案等所有可配置项。 + * + * ## 主要功能 + * + * - 系统信息 - 系统名称等基础信息 + * - 主题配置 - 亮色/暗色/自动主题的样式配置 + * - 菜单配置 - 菜单布局、主题、宽度等配置 + * - 颜色方案 - 系统主色和预设颜色列表 + * - 快速入口 - 快速入口应用和链接配置 + * - 顶部栏配置 - 顶部栏功能模块配置 + * + * ## 配置项说明 + * + * - systemInfo: 系统基础信息(名称等) + * - systemThemeStyles: 系统主题样式映射 + * - settingThemeList: 可选的系统主题列表 + * - menuLayoutList: 可选的菜单布局列表 + * - themeList: 菜单主题样式列表 + * - darkMenuStyles: 暗黑模式下的菜单样式 + * - systemMainColor: 预设的系统主色列表 + * - fastEnter: 快速入口配置 + * - headerBar: 顶部栏功能配置 + * + * @module config + * @author Art Design Pro Team + */ + +import { MenuThemeEnum, MenuTypeEnum, SystemThemeEnum } from '@/enums/appEnum' +import { SystemConfig } from '@/types/config' +import { configImages } from './assets/images' +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 +} + +export default Object.freeze(appConfig) diff --git a/adminSystem/src/config/modules/component.ts b/adminSystem/src/config/modules/component.ts new file mode 100644 index 0000000..bc709e0 --- /dev/null +++ b/adminSystem/src/config/modules/component.ts @@ -0,0 +1,105 @@ +/** + * 全局组件配置 + * + * 统一管理系统级全局组件的注册。 + * 这些组件会在应用启动时全局注册,可在任何地方使用。 + * + * ## 主要功能 + * + * - 组件配置 - 集中管理全局组件的配置信息 + * - 异步加载 - 使用 defineAsyncComponent 实现按需加载 + * - 开关控制 - 支持通过 enabled 字段启用/禁用组件 + * - 配置查询 - 提供工具函数快速查询组件配置 + * + * @module config/component + * @author Art Design Pro Team + */ + +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 + } +] + +/** + * 全局组件配置接口 + */ +export interface GlobalComponentConfig { + /** 组件名称 */ + name: string + /** 组件标识 */ + key: string + /** 组件 */ + component: any + /** 是否启用 */ + enabled?: boolean + /** 组件描述 */ + description?: string +} + +/** + * 获取启用的全局组件 + * @returns 已启用的组件配置列表 + */ +export const getEnabledGlobalComponents = () => { + return globalComponentsConfig.filter((config) => config.enabled !== false) +} + +/** + * 根据 key 获取组件配置 + * @param key 组件标识 + * @returns 组件配置对象 + */ +export const getGlobalComponentByKey = (key: string) => { + return globalComponentsConfig.find((config) => config.key === key) +} diff --git a/adminSystem/src/config/modules/fastEnter.ts b/adminSystem/src/config/modules/fastEnter.ts new file mode 100644 index 0000000..6b9740c --- /dev/null +++ b/adminSystem/src/config/modules/fastEnter.ts @@ -0,0 +1,127 @@ +/** + * 快速入口配置 + * 包含:应用列表、快速链接等配置 + */ +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' + } + ] +} + +export default Object.freeze(fastEnterConfig) diff --git a/adminSystem/src/config/modules/festival.ts b/adminSystem/src/config/modules/festival.ts new file mode 100644 index 0000000..39cd790 --- /dev/null +++ b/adminSystem/src/config/modules/festival.ts @@ -0,0 +1,51 @@ +/** + * 节日庆祝配置 + * + * 配置系统的节日烟花效果和祝福文本。 + * 支持单日节日和跨日期节日,可自定义烟花播放次数。 + * + * ## 配置说明 + * + * - name: 节日名称 + * - date: 节日开始日期(格式:YYYY-MM-DD) + * - endDate: 节日结束日期(可选,用于跨日期节日) + * - image: 烟花图片(需要预先导入) + * - scrollText: 滚动显示的祝福文本 + * - count: 烟花播放次数(可选,默认为 3 次) + * + * ## 注意事项 + * + * - 图片需要预先导入并在配置中引用 + * - 跨日期节日会在整个日期范围内生效 + * - 每个用户每天只会播放一次烟花效果 + * + * @module config/modules/festival + * @author Art Design Pro Team + */ + +import { FestivalConfig } from '@/types/config' + +// 导入烟花图片(根据需要取消注释) +// import sd from '@imgs/ceremony/sd.png' +// 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 祝您圣诞快乐,愿节日的欢乐与祝福如雪花般纷至沓来!', + // } +] diff --git a/adminSystem/src/config/modules/headerBar.ts b/adminSystem/src/config/modules/headerBar.ts new file mode 100644 index 0000000..a420e82 --- /dev/null +++ b/adminSystem/src/config/modules/headerBar.ts @@ -0,0 +1,63 @@ +/** + * 顶部栏功能配置 + * + * 统一管理顶部栏各个功能模块的启用状态。 + * 通过修改此配置文件可以快速启用或禁用顶部栏的功能按钮。 + * + * @module config/headerBar + * @author Art Design Pro Team + */ + +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: '主题切换功能(明暗主题)' + } +} + +export default headerBarConfig diff --git a/adminSystem/src/config/setting.ts b/adminSystem/src/config/setting.ts new file mode 100644 index 0000000..94f2d2c --- /dev/null +++ b/adminSystem/src/config/setting.ts @@ -0,0 +1,109 @@ +/** + * 系统设置默认值配置 + * + * 统一管理系统设置的所有默认值 + * + * ## 主要功能 + * + * - 菜单相关默认配置 + * - 主题相关默认配置 + * - 界面显示默认配置 + * - 功能开关默认配置 + * - 样式相关默认配置 + * + * ## 注意事项 + * + * 1. 修改此文件的配置项时,需要同步更新以下文件: + * - src/components/core/layouts/art-settings-panel/widget/SettingActions.vue(复制配置和重置配置逻辑) + * - src/store/modules/setting.ts(Store 状态定义) + * 2. 可以通过设置面板的"复制配置"按钮快速生成配置代码 + * 3. 枚举类型的值需要与 src/enums/appEnum.ts 中的定义保持一致 + */ + +import AppConfig from '@/config' +import { SystemThemeEnum, MenuThemeEnum, MenuTypeEnum, ContainerWidthEnum } from '@/enums/appEnum' + +/** + * 系统设置默认值配置 + */ +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: '' +} + +/** + * 获取设置默认值 + * @returns 设置默认值对象 + */ +export function getSettingDefaults() { + return { ...SETTING_DEFAULT_CONFIG } +} + +/** + * 重置为默认设置 + * @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] + } + }) +} diff --git a/adminSystem/src/directives/business/highlight.ts b/adminSystem/src/directives/business/highlight.ts new file mode 100644 index 0000000..13af225 --- /dev/null +++ b/adminSystem/src/directives/business/highlight.ts @@ -0,0 +1,248 @@ +/** + * v-highlight 代码高亮指令 + * + * 为代码块提供语法高亮、行号显示和一键复制功能。 + * 基于 highlight.js 实现,支持多种编程语言的语法高亮。 + * + * ## 主要功能 + * + * - 语法高亮 - 使用 highlight.js 自动识别并高亮代码 + * - 行号显示 - 自动为每行代码添加行号 + * - 一键复制 - 提供复制按钮,点击即可复制代码(自动过滤行号) + * - 性能优化 - 批量处理代码块,避免阻塞渲染 + * - 动态监听 - 使用 MutationObserver 监听新增代码块 + * - 防重复处理 - 自动标记已处理的代码块,避免重复处理 + * + * ## 使用示例 + * + * ```vue + * + * ``` + * + * ## 性能优化 + * + * - 批量处理:每次处理 10 个代码块,避免长时间阻塞 + * - 延迟处理:使用 requestAnimationFrame 分批处理 + * - 重试机制:自动重试处理失败的代码块 + * - 智能监听:只在有新代码块时才触发处理 + * + * @module directives/highlight + * @author Art Design Pro Team + */ + +import { App, Directive } from 'vue' +import hljs from 'highlight.js' + +// 高亮代码 +function highlightCode(block: HTMLElement) { + 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 +} + +// 添加复制按钮:调整 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 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') + ) +} + +// 标记代码块为已处理 +function markBlockAsProcessed(block: HTMLElement) { + block.setAttribute('data-highlighted', 'true') +} + +// 处理单个代码块 +function processBlock(block: HTMLElement) { + if (isBlockProcessed(block)) { + return + } + + 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)) + + if (unprocessedBlocks.length === 0) { + return + } + + 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) + + batch.forEach((block) => { + processBlock(block) + }) + + // 更新索引并继续处理下一批 + currentIndex += batchSize + if (currentIndex < unprocessedBlocks.length) { + // 使用 requestAnimationFrame 确保下一帧再处理 + requestAnimationFrame(processBatch) + } + } + + // 开始处理第一批代码块 + processBatch() + } +} + +// 重试处理函数 +function retryProcessing(el: HTMLElement, maxRetries: number = 3, delay: number = 200) { + let retryCount = 0 + + const tryProcess = () => { + processAllCodeBlocks(el) + + // 检查是否还有未处理的代码块 + const remainingBlocks = Array.from(el.querySelectorAll('pre code')).filter( + (block) => !isBlockProcessed(block) + ) + + if (remainingBlocks.length > 0 && retryCount < maxRetries) { + retryCount++ + setTimeout(tryProcess, delay * retryCount) // 递增延迟 + } + } + + tryProcess() +} + +// 代码高亮、插入行号、复制按钮 +const highlightDirective: Directive = { + mounted(el: HTMLElement) { + // 立即尝试处理一次 + processAllCodeBlocks(el) + + // 延迟处理,确保 v-html 内容已经渲染 + setTimeout(() => { + retryProcessing(el) + }, 100) + + // 使用 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 + } + } + }) + } + }) + + if (hasNewCodeBlocks) { + // 延迟处理新添加的代码块 + setTimeout(() => { + processAllCodeBlocks(el) + }, 50) + } + }) + + // 开始观察 + observer.observe(el, { + childList: true, + subtree: true + }) + + // 将 observer 存储到元素上,以便在 unmounted 时清理 + ;(el as any)._highlightObserver = observer + }, + + 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 + } + } +} + +export function setupHighlightDirective(app: App) { + app.directive('highlight', highlightDirective) +} diff --git a/adminSystem/src/directives/business/ripple.ts b/adminSystem/src/directives/business/ripple.ts new file mode 100644 index 0000000..8d7d8f9 --- /dev/null +++ b/adminSystem/src/directives/business/ripple.ts @@ -0,0 +1,114 @@ +/** + * v-ripple 水波纹效果指令 + * + * 为元素添加 Material Design 风格的水波纹点击效果。 + * 点击时从点击位置扩散出圆形水波纹动画,提升交互体验。 + * + * ## 主要功能 + * + * - 水波纹动画 - 点击时从点击位置扩散圆形波纹 + * - 自适应大小 - 根据元素尺寸自动调整波纹大小和动画时长 + * - 智能配色 - 自动识别按钮类型,使用合适的波纹颜色 + * - 自定义颜色 - 支持通过参数自定义波纹颜色 + * - 性能优化 - 使用 requestAnimationFrame 和自动清理机制 + * + * ## 使用示例 + * + * ```vue + * + * ``` + * + * ## 颜色规则 + * + * - 有色按钮(primary、success、warning 等):使用白色半透明波纹 + * - 默认按钮:使用主题色半透明波纹 + * - 自定义:通过 color 参数指定任意颜色 + * + * @module directives/ripple + * @author Art Design Pro Team + */ + +import type { App, Directive, DirectiveBinding } from 'vue' + +export interface RippleOptions { + /** 水波纹颜色 */ + color?: string +} + +export const vRipple: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + // 获取指令的配置参数 + const options: RippleOptions = binding.value || {} + + // 设置元素为相对定位,并隐藏溢出部分 + 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 + + // 创建水波纹元素 + 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 + + // 设置水波纹的尺寸和位置 + 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)' // 默认按钮使用主题色水波纹 + + // 设置水波纹颜色、初始状态和过渡效果 + 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) + + // 触发动画 + requestAnimationFrame(() => { + ripple.style.transform = 'scale(2)' + ripple.style.opacity = '0' + }) + + // 动画结束后移除水波纹元素 + setTimeout(() => { + ripple.remove() + }, animationDuration + 500) // 增加500ms缓冲时间 + }) + } +} + +export function setupRippleDirective(app: App) { + app.directive('ripple', vRipple) +} diff --git a/adminSystem/src/directives/core/auth.ts b/adminSystem/src/directives/core/auth.ts new file mode 100644 index 0000000..b9e85d8 --- /dev/null +++ b/adminSystem/src/directives/core/auth.ts @@ -0,0 +1,68 @@ +/** + * v-auth 权限指令 + * + * 适用于后端权限控制模式,基于权限标识控制 DOM 元素的显示和隐藏。 + * 如果用户没有对应权限,元素将从 DOM 中移除。 + * + * ## 主要功能 + * + * - 权限验证 - 根据路由 meta 中的权限列表验证用户权限 + * - DOM 控制 - 无权限时自动移除元素,而非隐藏 + * - 响应式更新 - 权限变化时自动更新元素状态 + * + * ## 使用示例 + * + * ```vue + * + * 新增 + * + * + * 编辑 + * + * + * 删除 + * ``` + * + * ## 注意事项 + * + * - 该指令会直接移除 DOM 元素,而不是使用 v-if 隐藏 + * - 权限列表从当前路由的 meta.authList 中获取 + * + * @module directives/auth + * @author Art Design Pro Team + */ + +import { router } from '@/router' +import { App, Directive, DirectiveBinding } from 'vue' + +interface AuthBinding extends DirectiveBinding { + value: string +} + +function checkAuthPermission(el: HTMLElement, binding: AuthBinding): void { + // 获取当前路由的权限列表 + const authList = (router.currentRoute.value.meta.authList as Array<{ authMark: string }>) || [] + + // 检查是否有对应的权限标识 + const hasPermission = authList.some((item) => item.authMark === binding.value) + + // 如果没有权限,移除元素 + if (!hasPermission) { + removeElement(el) + } +} + +function removeElement(el: HTMLElement): void { + if (el.parentNode) { + el.parentNode.removeChild(el) + } +} + +const authDirective: Directive = { + mounted: checkAuthPermission, + updated: checkAuthPermission +} + +export function setupAuthDirective(app: App): void { + app.directive('auth', authDirective) +} diff --git a/adminSystem/src/directives/core/roles.ts b/adminSystem/src/directives/core/roles.ts new file mode 100644 index 0000000..2ab1029 --- /dev/null +++ b/adminSystem/src/directives/core/roles.ts @@ -0,0 +1,89 @@ +/** + * v-roles 角色权限指令 + * + * 基于用户角色控制 DOM 元素的显示和隐藏。 + * 只要用户拥有指定角色中的任意一个,元素就会显示,否则从 DOM 中移除。 + * + * ## 主要功能 + * + * - 角色验证 - 检查用户是否拥有指定角色 + * - 多角色支持 - 支持单个角色或多个角色(满足其一即可) + * - DOM 控制 - 无权限时自动移除元素,而非隐藏 + * - 响应式更新 - 角色变化时自动更新元素状态 + * + * ## 使用示例 + * + * ```vue + * + * ``` + * + * ## 权限逻辑 + * + * - 用户角色从 userStore.getUserInfo.roles 获取 + * - 只要用户拥有指定角色中的任意一个,元素就会显示 + * - 如果用户没有任何角色或不满足条件,元素将被移除 + * + * ## 注意事项 + * + * - 该指令会直接移除 DOM 元素,而不是使用 v-if 隐藏 + * - 适用于基于角色的粗粒度权限控制 + * - 如需基于具体操作的细粒度权限控制,请使用 v-auth 指令 + * + * @module directives/roles + * @author Art Design Pro Team + */ + +import { useUserStore } from '@/store/modules/user' +import { App, Directive, DirectiveBinding } from 'vue' + +interface RolesBinding extends DirectiveBinding { + value: string | string[] +} + +function checkRolePermission(el: HTMLElement, binding: RolesBinding): void { + const userStore = useUserStore() + const userRoles = userStore.getUserInfo.roles + + // 如果用户角色为空或未定义,移除元素 + if (!userRoles?.length) { + removeElement(el) + return + } + + // 确保指令值为数组格式 + const requiredRoles = Array.isArray(binding.value) ? binding.value : [binding.value] + + // 检查用户是否具有所需角色之一 + const hasPermission = requiredRoles.some((role: string) => userRoles.includes(role)) + + // 如果没有权限,安全地移除元素 + if (!hasPermission) { + removeElement(el) + } +} + +function removeElement(el: HTMLElement): void { + if (el.parentNode) { + el.parentNode.removeChild(el) + } +} + +const rolesDirective: Directive = { + mounted: checkRolePermission, + updated: checkRolePermission +} + +export function setupRolesDirective(app: App): void { + app.directive('roles', rolesDirective) +} diff --git a/adminSystem/src/directives/index.ts b/adminSystem/src/directives/index.ts new file mode 100644 index 0000000..780464b --- /dev/null +++ b/adminSystem/src/directives/index.ts @@ -0,0 +1,12 @@ +import type { App } from 'vue' +import { setupAuthDirective } from './core/auth' +import { setupHighlightDirective } from './business/highlight' +import { setupRippleDirective } from './business/ripple' +import { setupRolesDirective } from './core/roles' + +export function setupGlobDirectives(app: App) { + setupAuthDirective(app) // 权限指令 + setupRolesDirective(app) // 角色权限指令 + setupHighlightDirective(app) // 高亮指令 + setupRippleDirective(app) // 水波纹指令 +} diff --git a/adminSystem/src/enums/appEnum.ts b/adminSystem/src/enums/appEnum.ts new file mode 100644 index 0000000..a39c278 --- /dev/null +++ b/adminSystem/src/enums/appEnum.ts @@ -0,0 +1,81 @@ +/** + * 系统级别枚举定义模块 + * + * ## 主要功能 + * + * - 菜单类型枚举(左侧、顶部、混合、双栏) + * - 主题类型枚举(亮色、暗色、自动) + * - 菜单主题枚举(设计、亮色、暗色) + * - 语言类型枚举(中文、英文) + * - 容器宽度枚举(全屏、固定) + * - 菜单宽度枚举(收起宽度) + * + * @module enums/appEnum + * @author Art Design Pro Team + */ + +/** + * 菜单类型 + */ +export enum MenuTypeEnum { + /** 左侧菜单 */ + LEFT = 'left', + /** 顶部菜单 */ + TOP = 'top', + /** 顶部+左侧菜单 */ + TOP_LEFT = 'top-left', + /** 双栏菜单 */ + DUAL_MENU = 'dual-menu' +} + +/** + * 系统主题 + */ +export enum SystemThemeEnum { + /** 暗色主题 */ + DARK = 'dark', + /** 亮色主题 */ + LIGHT = 'light', + /** 自动主题(跟随系统) */ + AUTO = 'auto' +} + +/** + * 菜单主题 + */ +export enum MenuThemeEnum { + /** 暗色主题 */ + DARK = 'dark', + /** 亮色主题 */ + LIGHT = 'light', + /** 设计主题 */ + DESIGN = 'design' +} + +/** + * 菜单宽度 + */ +export enum MenuWidth { + /** 收起宽度 */ + CLOSE = '64px' +} + +/** + * 语言类型 + */ +export enum LanguageEnum { + /** 中文 */ + ZH = 'zh', + /** 英文 */ + EN = 'en' +} + +/** + * 容器宽度 + */ +export enum ContainerWidthEnum { + /** 全屏宽度 */ + FULL = '100%', + /** 固定宽度 */ + BOXED = '1200px' +} diff --git a/adminSystem/src/enums/formEnum.ts b/adminSystem/src/enums/formEnum.ts new file mode 100644 index 0000000..8e9b3b4 --- /dev/null +++ b/adminSystem/src/enums/formEnum.ts @@ -0,0 +1,24 @@ +/** + * 表单相关枚举定义模块 + * + * ## 主要功能 + * + * - 页面模式枚举(新增、编辑) + * - 表格尺寸枚举(默认、紧凑、宽松) + * + * @module enums/formEnum + * @author Art Design Pro Team + */ + +// 页面类型 +export enum PageModeEnum { + Add, // 新增 + Edit // 编辑 +} + +// 表格大小 +export enum TableSizeEnum { + DEFAULT = 'default', + SMALL = 'small', + LARGE = 'large' +} diff --git a/adminSystem/src/env.d.ts b/adminSystem/src/env.d.ts new file mode 100644 index 0000000..4401f21 --- /dev/null +++ b/adminSystem/src/env.d.ts @@ -0,0 +1,34 @@ +/// + +declare module 'nprogress' + +declare module 'crypto-js' + +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 +} + +// 全局变量声明 +declare const __APP_VERSION__: string // 版本号 diff --git a/adminSystem/src/hooks/core/useAppMode.ts b/adminSystem/src/hooks/core/useAppMode.ts new file mode 100644 index 0000000..c39cd9e --- /dev/null +++ b/adminSystem/src/hooks/core/useAppMode.ts @@ -0,0 +1,45 @@ +/** + * useAppMode - 应用模式管理 + * + * 提供应用访问模式的判断和管理功能,支持前端和后端两种权限控制模式。 + * 根据环境变量 VITE_ACCESS_MODE 自动识别当前运行模式。 + * + * ## 主要功能 + * + * 1. 模式识别 - 自动识别前端模式或后端模式 + * 2. 前端模式 - 权限由前端路由配置控制,适合小型项目或演示环境 + * 3. 后端模式 - 权限由后端接口返回的菜单数据控制,适合企业级应用 + * 4. 响应式状态 - 提供响应式的模式判断,方便在组件中使用 + * + * @module useAppMode + * @author Art Design Pro Team + */ + +import { computed } from 'vue' + +export function useAppMode() { + // 获取访问模式配置 + const accessMode = import.meta.env.VITE_ACCESS_MODE + + /** + * 是否为前端控制模式 + * 前端模式:权限由前端路由配置控制 + */ + const isFrontendMode = computed(() => accessMode === 'frontend') + /** + * 是否为后端控制模式 + * 后端模式:权限由后端接口返回的菜单数据控制 + */ + const isBackendMode = computed(() => accessMode === 'backend') + + /** + * 当前应用模式 + */ + const currentMode = computed(() => accessMode) + + return { + isFrontendMode, + isBackendMode, + currentMode + } +} diff --git a/adminSystem/src/hooks/core/useAuth.ts b/adminSystem/src/hooks/core/useAuth.ts new file mode 100644 index 0000000..283b859 --- /dev/null +++ b/adminSystem/src/hooks/core/useAuth.ts @@ -0,0 +1,74 @@ +/** + * useAuth - 权限验证管理 + * + * 提供统一的权限验证功能,支持前端和后端两种权限模式。 + * 用于控制页面按钮、操作等功能的显示和访问权限。 + * + * ## 主要功能 + * + * 1. 权限检查 - 检查用户是否拥有指定的权限标识 + * 2. 双模式支持 - 自动适配前端模式和后端模式的权限验证 + * 3. 前端模式 - 从用户信息中获取按钮权限列表(如 ['add', 'edit', 'delete']) + * 4. 后端模式 - 从路由 meta 配置中获取权限列表(如 [{ authMark: 'add' }]) + * + * ## 使用示例 + * + * ```typescript + * const { hasAuth } = useAuth() + * + * // 检查是否有新增权限 + * if (hasAuth('add')) { + * // 显示新增按钮 + * } + * + * // 在模板中使用 + * 编辑 + * 删除 + * ``` + * + * @module useAuth + * @author Art Design Pro Team + */ + +import { useRoute } from 'vue-router' +import { storeToRefs } from 'pinia' +import { useUserStore } from '@/store/modules/user' +import { useAppMode } from '@/hooks/core/useAppMode' +import type { AppRouteRecord } from '@/types/router' + +type AuthItem = NonNullable[number] + +const userStore = useUserStore() + +export const useAuth = () => { + const route = useRoute() + const { isFrontendMode } = useAppMode() + const { info } = storeToRefs(userStore) + + // 前端按钮权限(例如:['add', 'edit']) + const frontendAuthList = info.value?.buttons ?? [] + + // 后端路由 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) + } + + // 后端模式 + return backendAuthList.some((item) => item?.authMark === auth) + } + + return { + hasAuth + } +} diff --git a/adminSystem/src/hooks/core/useCeremony.ts b/adminSystem/src/hooks/core/useCeremony.ts new file mode 100644 index 0000000..ead2630 --- /dev/null +++ b/adminSystem/src/hooks/core/useCeremony.ts @@ -0,0 +1,184 @@ +/** + * useCeremony - 节日庆祝管理 + * + * 提供节日烟花效果和祝福文本展示功能,为系统增添节日氛围。 + * 自动检测当前日期是否为节日,并在首次进入时播放烟花动画和显示祝福语。 + * + * ## 主要功能 + * + * 1. 节日检测 - 自动匹配当前日期与节日配置列表,支持单日和跨日期节日 + * 2. 烟花动画 - 播放节日烟花特效,支持自定义图片和触发次数 + * 3. 祝福文本 - 烟花结束后显示节日祝福文本 + * 4. 状态管理 - 记录烟花播放状态,避免重复播放 + * 5. 清理机制 - 提供清理方法,支持手动停止和重置 + * + * ## 使用示例 + * + * ```typescript + * // 在配置文件中定义节日 + * // 单日节日 + * { + * date: '2024-12-25', + * name: '圣诞节', + * image: christmasImage, + * count: 3 // 可选,不设置则使用默认值 3 次 + * scrollText: 'Merry Christmas!', + * } + * + * // 跨日期节日 + * { + * date: '2025-11-07', + * endDate: '2025-11-10', + * name: 'v3.0 测试阶段', + * image: '', + * count: 5 // 自定义烟花播放次数 + * scrollText: '系统 v3.0 测试阶段正式开启!', + * } + * ``` + * + * @module useCeremony + * @author Art Design Pro Team + */ + +import { useTimeoutFn, useIntervalFn, useDateFormat } from '@vueuse/core' +import { storeToRefs } from 'pinia' +import { computed } from 'vue' +import { useSettingStore } from '@/store/modules/setting' +import { mittBus } from '@/utils/sys' +import { festivalConfigList } from '@/config/modules/festival' + +/** + * 节日庆祝配置常量 + */ +const FESTIVAL_CONFIG = { + /** 初始延迟(毫秒) */ + INITIAL_DELAY: 300, + /** 烟花播放间隔(毫秒) */ + FIREWORK_INTERVAL: 1000, + /** 文本显示延迟(毫秒) */ + TEXT_DELAY: 2000, + /** 默认烟花播放次数 */ + DEFAULT_FIREWORKS_COUNT: 3 +} as const + +/** + * 节日庆祝功能 + * 提供节日烟花效果和祝福文本展示 + */ +export function useCeremony() { + const settingStore = useSettingStore() + const { holidayFireworksLoaded, isShowFireworks } = storeToRefs(settingStore) + + 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 + } + + // 跨日期节日 + const current = new Date(currentDate) + const start = new Date(festivalDate) + const end = new Date(festivalEndDate) + + 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)) + }) + + /** + * 更新节日日期到 store + */ + const updateFestivalDate = () => { + settingStore.setFestivalDate(currentFestivalData.value?.date || '') + } + + /** + * 触发烟花效果 + */ + const triggerFirework = () => { + mittBus.emit('triggerFireworks', currentFestivalData.value?.image) + } + + /** + * 完成烟花效果后显示文本 + */ + const showFestivalText = () => { + settingStore.setholidayFireworksLoaded(true) + + 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 { pause } = useIntervalFn(() => { + triggerFirework() + playedCount++ + + if (playedCount >= count) { + pause() + showFestivalText() + } + }, FESTIVAL_CONFIG.FIREWORK_INTERVAL) + + fireworksInterval = { pause } + } + + /** + * 开启节日庆祝 + */ + const openFestival = () => { + if (!currentFestivalData.value || !isShowFireworks.value) { + return + } + + const { start } = useTimeoutFn(startFireworksLoop, FESTIVAL_CONFIG.INITIAL_DELAY) + start() + } + + /** + * 清理烟花效果 + */ + const cleanup = () => { + if (fireworksInterval) { + fireworksInterval.pause() + fireworksInterval = null + } + settingStore.setShowFestivalText(false) + updateFestivalDate() + } + + return { + openFestival, + cleanup, + holidayFireworksLoaded, + currentFestivalData, + isShowFireworks + } +} diff --git a/adminSystem/src/hooks/core/useChart.ts b/adminSystem/src/hooks/core/useChart.ts new file mode 100644 index 0000000..29ba1d1 --- /dev/null +++ b/adminSystem/src/hooks/core/useChart.ts @@ -0,0 +1,745 @@ +/** + * useChart - ECharts 图表管理 + * + * 提供完整的 ECharts 图表生命周期管理和配置能力,简化图表开发流程。 + * 自动处理图表初始化、更新、销毁、主题切换、响应式调整等复杂逻辑。 + * + * ## 核心功能 + * + * 1. 图表生命周期管理 - 自动处理初始化、更新、销毁,支持延迟加载和可见性检测 + * 2. 主题自动适配 - 响应系统主题变化,自动更新图表样式和配色 + * 3. 响应式调整 - 监听窗口大小、菜单展开等变化,自动调整图表尺寸 + * 4. 空状态处理 - 优雅的空数据展示,自动显示"暂无数据"提示 + * 5. 样式配置统一 - 提供坐标轴、图例、提示框等统一的样式配置方法 + * 6. 性能优化 - 防抖处理、样式缓存、requestAnimationFrame 优化 + * 7. 高级组件抽象 - useChartComponent 提供更高层次的图表组件封装 + * + * ## 使用示例 + * + * ```typescript + * // 基础用法 + * const { + * chartRef, + * initChart, + * updateChart, + * getAxisLineStyle, + * getTooltipStyle + * } = useChart() + * + * onMounted(() => { + * initChart({ + * xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] }, + * yAxis: { type: 'value' }, + * series: [{ data: [120, 200, 150], type: 'bar' }] + * }) + * }) + * + * // 高级用法 - 组件抽象 + * const chart = useChartComponent({ + * props, + * generateOptions: () => ({ + * // ECharts 配置 + * }), + * checkEmpty: () => data.value.length === 0, + * watchSources: [() => props.data] + * }) + * ``` + * + * @module useChart + * @author Art Design Pro Team + */ + +import { echarts, type EChartsOption } from '@/plugins/echarts' +import { storeToRefs } from 'pinia' +import { useSettingStore } from '@/store/modules/setting' +import { getCssVar } from '@/utils/ui' +import type { BaseChartProps, ChartThemeConfig, UseChartOptions } from '@/types/component/chart' + +// 图表主题配置 +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' + ] +}) + +// 常量定义 +const RESIZE_DELAYS = [50, 100, 200, 350] as const +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 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 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 + }) + } + + // 防抖的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) + + // 使用延迟时间,确保图表正确适应变化 + delays.forEach((delay) => { + setTimeout(requestAnimationResize, delay) + }) + } + + // 收缩菜单时,重新计算图表大小(仅在图表存在时监听) + 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 cleanupMenuWatchers = () => { + menuOpenStopHandle?.() + menuTypeStopHandle?.() + menuOpenStopHandle = null + menuTypeStopHandle = null + } + + // 主题变化时重新设置图表选项 + let themeStopHandle: (() => void) | null = null + + 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) + } + } + }) + } + }) + } + } + + const cleanupThemeWatcher = () => { + themeStopHandle?.() + themeStopHandle = null + } + + // 样式生成器 - 统一的样式配置 + 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 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 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 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 + }) + + // 获取统一的 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 + } + + // 根据位置设置不同的配置 + 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 + } + + 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 + } + } + + // 创建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) + } + + // 触发自定义事件,让组件处理动画逻辑 + const event = new CustomEvent('chartVisible', { + detail: { options: pendingOptions } + }) + entry.target.dispatchEvent(event) + + pendingOptions = null + cleanupIntersectionObserver() + } catch (error) { + console.error('图表初始化失败:', error) + } + } + }) + } + }) + }, + { threshold } + ) + + intersectionObserver.observe(chartRef.value) + } + + // 清理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 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 + + emptyStateDiv = document.createElement('div') + emptyStateDiv.style.cssText = ` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 12px; + color: ${isDark.value ? '#555555' : '#B3B2B2'}; + background: transparent; + z-index: 10; + ` + emptyStateDiv.innerHTML = `暂无数据` + + // 确保父容器有相对定位 + if ( + chartRef.value.style.position !== 'relative' && + chartRef.value.style.position !== 'absolute' + ) { + chartRef.value.style.position = 'relative' + } + + chartRef.value.appendChild(emptyStateDiv) + }, + + remove: () => { + if (emptyStateDiv && chartRef.value) { + chartRef.value.removeChild(emptyStateDiv) + emptyStateDiv = null + } + }, + + updateStyle: () => { + if (emptyStateDiv) { + emptyStateDiv.style.color = isDark.value ? '#666' : '#999' + } + } + } + + // 初始化图表 + const initChart = (options: EChartsOption = {}, isEmpty: boolean = false) => { + if (!chartRef.value || isDestroyed) return + + const mergedOptions = { ...initOptions, ...options } + + 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) + } + } + + // 更新图表 + const updateChart = (options: EChartsOption) => { + if (isDestroyed) return + + 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 destroyChart = () => { + isDestroyed = true + + if (chart) { + try { + chart.dispose() + } catch (error) { + console.error('图表销毁失败:', error) + } finally { + chart = null + } + } + + // 清理所有监听器和资源 + cleanupMenuWatchers() + cleanupThemeWatcher() + emptyStateManager.remove() + cleanupIntersectionObserver() + clearTimers() + clearStyleCache() + pendingOptions = null + } + + // 获取图表实例 + const getChartInstance = () => chart + + // 获取图表是否已初始化 + const isChartInitialized = () => chart !== null + + onMounted(() => { + window.addEventListener('resize', debouncedResize) + }) + + onBeforeUnmount(() => { + window.removeEventListener('resize', debouncedResize) + }) + + onUnmounted(() => { + destroyChart() + }) + + 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 +} + +export function useChartComponent(options: UseChartComponentOptions) { + const { + props, + generateOptions, + checkEmpty, + watchSources = [], + onVisible, + chartOptions = {} + } = options + + 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 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 stopHandles: (() => void)[] = [] + + // 设置数据监听 + 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 cleanupWatchers = () => { + stopHandles.forEach((stop) => stop()) + stopHandles.length = 0 + } + + // 设置生命周期 + const setupLifecycle = () => { + onMounted(() => { + updateChart() + + // 监听图表可见事件 + if (chartRef.value) { + chartRef.value.addEventListener('chartVisible', handleChartVisible) + } + }) + + onBeforeUnmount(() => { + // 清理事件监听器 + if (chartRef.value) { + chartRef.value.removeEventListener('chartVisible', handleChartVisible) + } + // 清理所有监听器 + cleanupWatchers() + // 清理空状态div + emptyStateManager.remove() + }) + } + + // 初始化 + setupWatchers() + setupLifecycle() + + return { + ...chart, + isEmpty, + updateChart, + handleChartVisible + } +} diff --git a/adminSystem/src/hooks/core/useCommon.ts b/adminSystem/src/hooks/core/useCommon.ts new file mode 100644 index 0000000..c936854 --- /dev/null +++ b/adminSystem/src/hooks/core/useCommon.ts @@ -0,0 +1,87 @@ +/** + * useCommon - 通用功能集合 + * + * 提供常用的页面操作功能,包括页面刷新、滚动控制、路径获取等。 + * 这些功能在多个页面和组件中都会用到,统一封装便于复用。 + * + * ## 主要功能 + * + * 1. 首页路径 - 获取系统配置的首页路径 + * 2. 页面刷新 - 刷新当前页面内容 + * 3. 滚动控制 - 提供多种滚动到顶部和指定位置的方法 + * 4. 平滑滚动 - 支持平滑滚动动画效果 + * + * @module useCommon + * @author Art Design Pro Team + */ + +import { computed } from 'vue' +import { useMenuStore } from '@/store/modules/menu' +import { useSettingStore } from '@/store/modules/setting' + +export function useCommon() { + const menuStore = useMenuStore() + const settingStore = useSettingStore() + + /** + * 首页路径 + * 从菜单 store 中获取配置的首页路径 + */ + const homePath = computed(() => menuStore.getHomePath()) + + /** + * 刷新当前页面 + * 通过切换 setting store 中的 refresh 状态触发页面重新渲染 + */ + const refresh = () => { + settingStore.reload() + } + + /** + * 滚动到页面顶部 + * 查找主内容区域并将其滚动位置重置为顶部 + */ + 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' + }) + } + } + + /** + * 滚动到指定位置 + * @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 + } +} diff --git a/adminSystem/src/hooks/core/useFastEnter.ts b/adminSystem/src/hooks/core/useFastEnter.ts new file mode 100644 index 0000000..555eb65 --- /dev/null +++ b/adminSystem/src/hooks/core/useFastEnter.ts @@ -0,0 +1,55 @@ +/** + * useFastEnter - 快速入口管理 + * + * 管理顶部栏的快速入口功能,提供应用列表和快速链接的配置和过滤。 + * 支持动态启用/禁用、自定义排序、响应式宽度控制等功能。 + * + * ## 主要功能 + * + * 1. 应用列表管理 - 获取启用的应用列表,自动按排序权重排序 + * 2. 快速链接管理 - 获取启用的快速链接,支持自定义排序 + * 3. 响应式配置 - 所有配置自动响应变化,无需手动更新 + * 4. 宽度控制 - 提供最小显示宽度配置,支持响应式布局 + * + * @module useFastEnter + * @author Art Design Pro Team + */ + +import { computed } from 'vue' +import appConfig from '@/config' +import type { FastEnterApplication, FastEnterQuickLink } from '@/types/config' + +export function useFastEnter() { + // 获取快速入口配置 + const fastEnterConfig = computed(() => appConfig.fastEnter) + + // 获取启用的应用列表(按排序权重排序) + 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)) + }) + + // 获取启用的快速链接(按排序权重排序) + 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)) + }) + + // 获取最小显示宽度 + const minWidth = computed(() => { + return fastEnterConfig.value?.minWidth || 1200 + }) + + return { + fastEnterConfig, + enabledApplications, + enabledQuickLinks, + minWidth + } +} diff --git a/adminSystem/src/hooks/core/useHeaderBar.ts b/adminSystem/src/hooks/core/useHeaderBar.ts new file mode 100644 index 0000000..be10712 --- /dev/null +++ b/adminSystem/src/hooks/core/useHeaderBar.ts @@ -0,0 +1,201 @@ +/** + * useHeaderBar - 顶部栏功能管理 + * + * 统一管理顶部栏各个功能模块的显示状态和配置信息。 + * 提供灵活的功能开关控制,支持动态显示/隐藏顶部栏的各个功能按钮。 + * + * ## 主要功能 + * + * 1. 功能开关控制 - 统一管理菜单按钮、刷新按钮、快速入口等功能的显示状态 + * 2. 配置信息获取 - 获取各个功能模块的详细配置信息 + * 3. 功能列表查询 - 快速获取所有启用或禁用的功能列表 + * 4. 响应式状态 - 所有状态自动响应配置和 store 变化 + * + * @module useHeaderBar + * @author Art Design Pro Team + */ + +import { computed } from 'vue' +import { storeToRefs } from 'pinia' +import { useSettingStore } from '@/store/modules/setting' +import { headerBarConfig } from '@/config/modules/headerBar' +import { HeaderBarFeatureConfig } from '@/types' + +/** + * 顶部栏功能管理 + * @returns 顶部栏功能相关的状态和方法 + */ +export function useHeaderBar() { + const settingStore = useSettingStore() + + // 获取顶部栏配置 + const headerBarConfigRef = computed(() => headerBarConfig) + + // 从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 getFeatureConfig = (feature: keyof HeaderBarFeatureConfig) => { + return headerBarConfigRef.value[feature] + } + + // 检查菜单按钮是否显示 + const shouldShowMenuButton = computed(() => { + return isFeatureEnabled('menuButton') && showMenuButton.value + }) + + // 检查刷新按钮是否显示 + const shouldShowRefreshButton = computed(() => { + return isFeatureEnabled('refreshButton') && showRefreshButton.value + }) + + // 检查快速入口是否显示 + const shouldShowFastEnter = computed(() => { + return isFeatureEnabled('fastEnter') && showFastEnter.value + }) + + // 检查面包屑是否显示 + const shouldShowBreadcrumb = computed(() => { + return isFeatureEnabled('breadcrumb') && showCrumbs.value + }) + + // 检查全局搜索是否显示 + const shouldShowGlobalSearch = computed(() => { + return isFeatureEnabled('globalSearch') + }) + + // 检查全屏按钮是否显示 + const shouldShowFullscreen = computed(() => { + return isFeatureEnabled('fullscreen') + }) + + // 检查通知中心是否显示 + const shouldShowNotification = computed(() => { + return isFeatureEnabled('notification') + }) + + // 检查聊天功能是否显示 + const shouldShowChat = computed(() => { + return isFeatureEnabled('chat') + }) + + // 检查语言切换是否显示 + const shouldShowLanguage = computed(() => { + return isFeatureEnabled('language') && showLanguage.value + }) + + // 检查设置面板是否显示 + const shouldShowSettings = computed(() => { + return isFeatureEnabled('settings') + }) + + // 检查主题切换是否显示 + const shouldShowThemeToggle = computed(() => { + return isFeatureEnabled('themeToggle') + }) + + // 获取快速入口的最小宽度 + 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 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 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 getInactiveFeatures = () => { + return getDisabledFeatures() + } + + return { + // 配置 + headerBarConfig: headerBarConfigRef, + + // 显示状态计算属性 + shouldShowMenuButton, // 是否显示菜单按钮 + shouldShowRefreshButton, // 是否显示刷新按钮 + shouldShowFastEnter, // 是否显示快速入口 + shouldShowBreadcrumb, // 是否显示面包屑 + shouldShowGlobalSearch, // 是否显示全局搜索 + shouldShowFullscreen, // 是否显示全屏按钮 + shouldShowNotification, // 是否显示通知中心 + shouldShowChat, // 是否显示聊天功能 + shouldShowLanguage, // 是否显示语言切换 + shouldShowSettings, // 是否显示设置面板 + shouldShowThemeToggle, // 是否显示主题切换 + + // 配置相关 + fastEnterMinWidth, // 快速入口最小宽度 + + // 方法 + isFeatureEnabled, // 检查功能是否启用 + isFeatureActive, // 检查功能是否启用(别名) + getFeatureConfig, // 获取功能配置 + getFeatureInfo, // 获取功能配置(别名) + getEnabledFeatures, // 获取所有启用的功能 + getDisabledFeatures, // 获取所有禁用的功能 + getActiveFeatures, // 获取所有启用的功能(别名) + getInactiveFeatures // 获取所有禁用的功能(别名) + } +} diff --git a/adminSystem/src/hooks/core/useLayoutHeight.ts b/adminSystem/src/hooks/core/useLayoutHeight.ts new file mode 100644 index 0000000..4b1171a --- /dev/null +++ b/adminSystem/src/hooks/core/useLayoutHeight.ts @@ -0,0 +1,148 @@ +/** + * useLayoutHeight - 页面布局高度管理 + * + * 自动计算和管理页面内容区域的高度,确保内容区域能够正确填充剩余空间。 + * 监听头部元素高度变化,动态调整内容区域高度,避免出现滚动条或布局错乱。 + * + * ## 主要功能 + * + * 1. 动态高度计算 - 根据头部元素高度自动计算内容区域高度 + * 2. 响应式监听 - 自动监听元素尺寸变化并更新高度 + * 3. CSS 变量同步 - 自动更新 CSS 变量,方便全局使用 + * 4. 灵活配置 - 支持自定义间距、CSS 变量名等 + * 5. 自动查找模式 - 提供通过 ID 自动查找元素的便捷方式 + * + * @module useLayoutHeight + * @author Art Design Pro Team + */ + +import { ref, computed, watch, onMounted } from 'vue' +import { useElementSize } from '@vueuse/core' + +/** + * 页面容器高度配置 + */ +interface LayoutHeightOptions { + /** 额外的间距(默认 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 headerRef = ref() + const contentHeaderRef = ref() + + // 使用 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)` + }) + + if (updateCssVar) { + watch( + containerMinHeight, + (newHeight) => { + requestAnimationFrame(() => { + document.documentElement.style.setProperty(cssVarName, newHeight) + }) + }, + { immediate: true } + ) + } + + return { + /** 容器最小高度(响应式) */ + containerMinHeight, + /** 头部元素引用 */ + headerRef, + /** 内容头部元素引用 */ + contentHeaderRef, + /** 头部高度(响应式) */ + headerHeight, + /** 内容头部高度(响应式) */ + contentHeaderHeight + } +} + +/** + * 通过 ID 自动查找元素的布局高度管理 + * 适用于无法直接获取元素引用的场景 + * + * @param headerIds 头部元素的 ID 数组 + * @param options 配置选项 + * + * ``` + */ +export function useAutoLayoutHeight( + headerIds: string[] = ['app-header', 'app-content-header'], + options: LayoutHeightOptions = {} +) { + const { extraSpacing = 15, updateCssVar = true, cssVarName = '--art-full-height' } = options + + // 创建元素引用 + const headerRef = ref() + const contentHeaderRef = ref() + + // 使用 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)` + }) + + 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]) + + if (header) { + headerRef.value = header + } + if (contentHeader) { + contentHeaderRef.value = contentHeader + } + }) + } + }) + + return { + /** 容器最小高度(响应式) */ + containerMinHeight, + /** 头部元素引用 */ + headerRef, + /** 内容头部元素引用 */ + contentHeaderRef, + /** 头部高度(响应式) */ + headerHeight, + /** 内容头部高度(响应式) */ + contentHeaderHeight + } +} diff --git a/adminSystem/src/hooks/core/useTable.ts b/adminSystem/src/hooks/core/useTable.ts new file mode 100644 index 0000000..afe9ebe --- /dev/null +++ b/adminSystem/src/hooks/core/useTable.ts @@ -0,0 +1,736 @@ +/** + * useTable - 企业级表格数据管理方案 + * + * 功能完整的表格数据管理解决方案,专为后台管理系统设计。 + * 封装了表格开发中的所有常见需求,让你专注于业务逻辑。 + * + * ## 主要功能 + * + * 1. 数据管理 - 自动处理 API 请求、响应转换、加载状态和错误处理 + * 2. 分页控制 - 自动同步分页状态、移动端适配、智能页码边界处理 + * 3. 搜索功能 - 防抖搜索优化、参数管理、一键重置、参数过滤 + * 4. 缓存系统 - 智能请求缓存、多种清理策略、自动过期管理、统计信息 + * 5. 刷新策略 - 提供 5 种刷新方法适配不同业务场景(新增/更新/删除/手动/定时) + * 6. 列配置管理 - 动态显示/隐藏列、列排序、配置持久化、批量操作(可选) + * + * @module useTable + * @author Art Design Pro Team + */ + +import { ref, reactive, computed, onMounted, onUnmounted, nextTick, readonly } from 'vue' +import { useWindowSize } from '@vueuse/core' +import { useTableColumns } from './useTableColumns' +import type { ColumnOption } from '@/types/component' +import { + TableCache, + CacheInvalidationStrategy, + type ApiResponse +} from '../../utils/table/tableCache' +import { + type TableError, + defaultResponseAdapter, + extractTableData, + updatePaginationFromResponse, + createSmartDebounce, + createErrorHandler +} from '../../utils/table/tableUtils' +import { tableConfig } from '../../utils/table/tableConfig' + +// 类型推导工具类型 +type InferApiParams = T extends (params: infer P) => any ? P : never +type InferApiResponse = T extends (params: any) => Promise ? R : never +type InferRecordType = T extends Api.Common.PaginatedResponse ? U : never + +// 优化的配置接口 - 支持自动类型推导 +export interface UseTableConfig< + 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 + } + } + + // 数据处理 + transform?: { + /** 数据转换函数 */ + dataTransformer?: (data: TRecord[]) => TRecord[] + /** 响应数据适配器 */ + responseAdapter?: (response: TResponse) => ApiResponse + } + + // 性能优化 + 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 + } + + // 调试配置 + debug?: { + /** 是否启用日志输出 */ + enableLog?: boolean + /** 日志级别 */ + logLevel?: 'info' | 'warn' | 'error' + } +} + +export function useTable Promise>( + config: UseTableConfig +) { + return useTableImpl(config) +} + +/** + * useTable 的核心实现 - 强大的表格数据管理 Hook + * + * 提供完整的表格解决方案,包括: + * - 数据获取与缓存 + * - 分页控制 + * - 搜索功能 + * - 智能刷新策略 + * - 错误处理 + * - 列配置管理 + */ +function useTableImpl Promise>( + 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 + }) + } +} + +// 重新导出类型和枚举,方便使用 +export { CacheInvalidationStrategy } from '../../utils/table/tableCache' +export type { ApiResponse, CacheItem } from '../../utils/table/tableCache' +export type { BaseRequestParams, TableError } from '../../utils/table/tableUtils' diff --git a/adminSystem/src/hooks/core/useTableColumns.ts b/adminSystem/src/hooks/core/useTableColumns.ts new file mode 100644 index 0000000..e4daf92 --- /dev/null +++ b/adminSystem/src/hooks/core/useTableColumns.ts @@ -0,0 +1,256 @@ +/** + * useTableColumns - 表格列配置管理 + * + * 提供动态的表格列配置管理能力,支持运行时灵活控制列的显示、隐藏、排序等操作。 + * 通常与 useTable 配合使用,为表格提供完整的列管理功能。 + * + * ## 主要功能 + * + * 1. 列显示控制 - 动态显示/隐藏列,支持批量操作 + * 2. 列排序 - 拖拽或编程方式重新排列列顺序 + * 3. 列配置管理 - 新增、删除、更新列配置 + * 4. 特殊列支持 - 自动处理 selection、expand、index 等特殊列 + * 5. 状态持久化 - 保持列的显示状态,支持重置到初始状态 + * + * ## 使用示例 + * + * ```typescript + * const { columns, columnChecks, toggleColumn, reorderColumns } = useTableColumns(() => [ + * { prop: 'name', label: '姓名', visible: true }, + * { prop: 'email', label: '邮箱', visible: true }, + * { prop: 'status', label: '状态', visible: false } + * ]) + * + * // 切换列显示 + * toggleColumn('email', false) + * + * // 重新排序 + * reorderColumns(0, 2) + * ``` + * + * @module useTableColumns + * @author Art Design Pro Team + */ + +import { ref, computed, watch } from 'vue' +import { $t } from '@/locales' +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') } +} + +/** + * 获取列的唯一标识 + */ +export const getColumnKey = (col: ColumnOption) => + 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 +} + +/** + * 获取列的检查状态 + */ +export const getColumnChecks = (columns: ColumnOption[]) => + 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 } + }) + +/** + * 动态列配置接口 + */ +export interface DynamicColumnConfig { + /** + * 新增列 + * @param column 列配置 + * @param index 可选的插入位置,默认末尾 + */ + addColumn: (column: ColumnOption, index?: number) => void + /** + * 删除列 + * @param prop 列的唯一标识或标识数组 + */ + removeColumn: (prop: string | string[]) => void + /** + * 切换列显示状态 + * @param prop 列的唯一标识 + * @param visible 可选的显示状态,默认取反 + */ + toggleColumn: (prop: string, visible?: boolean) => void + + /** + * 更新列 + * @param prop 列的唯一标识 + * @param updates 列配置更新 + */ + updateColumn: (prop: string, updates: Partial>) => void + /** + * 批量更新列 + * @param updates 列更新配置 + */ + 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[] +): { + columns: any + columnChecks: any +} & DynamicColumnConfig { + 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 } + ) + + // 当前显示列(基于 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 + } + + return { + columns, + columnChecks, + + addColumn: (column: ColumnOption, index?: number) => + setDynamicColumns((cols) => { + const next = [...cols] + if (typeof index === 'number' && index >= 0 && index <= next.length) { + next.splice(index, 0, column) + } else { + next.push(column) + } + return next + }), + + removeColumn: (prop: string | string[]) => + setDynamicColumns((cols) => { + const propsToRemove = Array.isArray(prop) ? prop : [prop] + return cols.filter((c) => !propsToRemove.includes(getColumnKey(c))) + }), + + updateColumn: (prop: string, updates: Partial>) => + setDynamicColumns((cols) => + cols.map((c) => (getColumnKey(c) === prop ? { ...c, ...updates } : c)) + ), + + toggleColumn: (prop: string, visible?: boolean) => { + const i = columnChecks.value.findIndex((c) => getColumnKey(c) === prop) + if (i > -1) { + const next = [...columnChecks.value] + const currentVisibility = getColumnVisibility(next[i]) + const newVisibility = visible ?? !currentVisibility + // 同时更新 checked 和 visible 以保持兼容性 + next[i] = { ...next[i], checked: newVisibility, visible: newVisibility } + columnChecks.value = next + } + }, + + resetColumns: () => { + dynamicColumns.value = columnsFactory() + }, + + 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 + }), + + getColumnConfig: (prop: string) => dynamicColumns.value.find((c) => getColumnKey(c) === prop), + + getAllColumns: () => [...dynamicColumns.value] + } +} diff --git a/adminSystem/src/hooks/core/useTableHeight.ts b/adminSystem/src/hooks/core/useTableHeight.ts new file mode 100644 index 0000000..8fdf6da --- /dev/null +++ b/adminSystem/src/hooks/core/useTableHeight.ts @@ -0,0 +1,105 @@ +/** + * useTableHeight - 表格高度自动计算 + * + * 自动计算表格容器的最佳高度,确保表格在不同布局场景下都能正确显示。 + * 根据表格头部、分页器等元素的高度动态调整容器高度,避免出现滚动条或布局错乱。 + * + * ## 主要功能 + * + * 1. 动态高度计算 - 根据表格头部、分页器高度自动计算容器高度 + * 2. 响应式更新 - 配置变化时自动重新计算高度 + * 3. 灵活配置 - 支持自定义各部分高度和间距 + * 4. 智能适配 - 无额外元素时自动使用 100% 高度 + * + * @module useTableHeight + * @author Art Design Pro Team + */ + +import { computed, type Ref } from 'vue' + +/** + * 表格高度计算器配置接口 + */ +interface TableHeightOptions { + /** 是否显示表格头部 */ + 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 + + constructor(private options: TableHeightOptions) {} + + /** + * 计算容器高度 + */ + 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() + } + + const headerHeight = this.getHeaderHeight() + const paginationOffset = this.calculatePaginationOffset() + + return headerHeight + paginationOffset + TableHeightCalculator.TABLE_HEADER_SPACING + } + + /** + * 获取表格头部高度 + */ + 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 + } +} + +/** + * 表格高度计算 Hook + * + * 提供表格容器高度的自动计算功能,支持: + * - 表格头部高度 + * - 分页器高度 + * - 动态间距计算 + * + * @param options 配置选项 + * @returns 容器高度计算结果 + */ +export function useTableHeight(options: TableHeightOptions) { + const containerHeight = computed(() => { + const calculator = new TableHeightCalculator(options) + return calculator.calculate() + }) + + return { + /** 容器高度样式对象 */ + containerHeight + } +} diff --git a/adminSystem/src/hooks/core/useTheme.ts b/adminSystem/src/hooks/core/useTheme.ts new file mode 100644 index 0000000..187c3e0 --- /dev/null +++ b/adminSystem/src/hooks/core/useTheme.ts @@ -0,0 +1,174 @@ +/** + * useTheme - 系统主题管理 + * + * 提供完整的主题切换和管理功能,支持亮色、暗色和自动模式。 + * 自动处理主题切换时的过渡效果,确保切换流畅无闪烁。 + * + * ## 主要功能 + * + * 1. 主题切换 - 支持亮色、暗色、自动三种主题模式 + * 2. 自动模式 - 根据系统偏好自动切换主题 + * 3. 颜色适配 - 自动调整主题色的明暗变体(9 个层级) + * 4. 过渡优化 - 切换时临时禁用过渡效果,避免闪烁 + * 5. 状态持久化 - 主题设置自动保存到 store + * + * ## 使用示例 + * + * ```typescript + * const { switchThemeStyles } = useTheme() + * + * // 切换到暗色主题 + * switchThemeStyles(SystemThemeEnum.DARK) + * + * // 切换到亮色主题 + * switchThemeStyles(SystemThemeEnum.LIGHT) + * + * // 切换到自动模式(跟随系统) + * switchThemeStyles(SystemThemeEnum.AUTO) + * ``` + * + * @module useTheme + * @author Art Design Pro Team + */ + +import { useSettingStore } from '@/store/modules/setting' +import { SystemThemeEnum } from '@/enums/appEnum' +import AppConfig from '@/config' +import { SystemThemeTypes } from '@/types/store' +import { getDarkColor, getLightColor, setElementThemeColor } from '@/utils/ui' +import { usePreferredDark } from '@vueuse/core' +import { watch } from 'vue' + +export function useTheme() { + 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 enableTransitions = () => { + const style = document.getElementById('disable-transitions') + if (style) { + style.remove() + } + } + + // 设置系统主题 + const setSystemTheme = (theme: SystemThemeEnum, themeMode?: SystemThemeEnum) => { + // 临时禁用过渡效果 + disableTransitions() + + const el = document.getElementsByTagName('html')[0] + const isDark = theme === SystemThemeEnum.DARK + + if (!themeMode) { + themeMode = theme + } + + const currentTheme = AppConfig.systemThemeStyles[theme as keyof SystemThemeTypes] + + if (currentTheme) { + el.setAttribute('class', currentTheme.className) + } + + // 设置按钮颜色加深或变浅 + 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)}` + ) + } + + // 更新store中的主题设置 + settingStore.setGlopTheme(theme, themeMode) + + // 使用 requestAnimationFrame 确保在下一帧恢复过渡效果 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + enableTransitions() + }) + }) + } + + // 使用 VueUse 的 usePreferredDark 检测系统主题偏好 + const prefersDark = usePreferredDark() + + // 自动设置系统主题 + 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) + } + } + + return { + setSystemTheme, + setSystemAutoTheme, + switchThemeStyles, + prefersDark + } +} + +/** + * 初始化主题系统 + */ +export function initializeTheme() { + const settingStore = useSettingStore() + const prefersDark = usePreferredDark() + + // 根据系统偏好应用主题 + 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 + } + + // 设置主题 class + const currentTheme = AppConfig.systemThemeStyles[actualTheme as keyof SystemThemeTypes] + if (currentTheme) { + el.setAttribute('class', currentTheme.className) + } + + // 设置主题颜色 + setElementThemeColor(settingStore.systemThemeColor) + + // 设置圆角 + document.documentElement.style.setProperty('--custom-radius', `${settingStore.customRadius}rem`) + } + + // 应用主题 + applyThemeByMode() + + // 如果是 AUTO 模式,监听系统主题变化(使用 VueUse 的响应式特性) + if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { + watch( + prefersDark, + () => { + // 只有在 AUTO 模式下才响应系统主题变化 + if (settingStore.systemThemeMode === SystemThemeEnum.AUTO) { + applyThemeByMode() + } + }, + { immediate: false } + ) + } +} diff --git a/adminSystem/src/hooks/index.ts b/adminSystem/src/hooks/index.ts new file mode 100644 index 0000000..472b09c --- /dev/null +++ b/adminSystem/src/hooks/index.ts @@ -0,0 +1,32 @@ +// 通用功能集合 +export { useCommon } from './core/useCommon' + +// 应用模式 +export { useAppMode } from './core/useAppMode' + +// 权限控制 +export { useAuth } from './core/useAuth' + +// 表格数据管理方案 +export { useTable } from './core/useTable' + +// 表格列配置管理 +export { useTableColumns } from './core/useTableColumns' + +// 主题相关 +export { useTheme } from './core/useTheme' + +// 礼花+文字滚动 +export { useCeremony } from './core/useCeremony' + +// 顶栏快速入口 +export { useFastEnter } from './core/useFastEnter' + +// 顶栏功能管理 +export { useHeaderBar } from './core/useHeaderBar' + +// 图表相关 +export { useChart, useChartComponent, useChartOps } from './core/useChart' + +// 布局高度 +export { useLayoutHeight, useAutoLayoutHeight } from './core/useLayoutHeight' diff --git a/adminSystem/src/locales/index.ts b/adminSystem/src/locales/index.ts new file mode 100644 index 0000000..36c2648 --- /dev/null +++ b/adminSystem/src/locales/index.ts @@ -0,0 +1,123 @@ +/** + * 国际化配置 + * + * 基于 vue-i18n 实现的多语言国际化解决方案。 + * 支持中文和英文切换,自动从本地存储恢复用户的语言偏好。 + * + * ## 主要功能 + * + * - 多语言支持 - 支持中文(简体)和英文两种语言 + * - 语言切换 - 运行时动态切换语言,无需刷新页面 + * - 持久化存储 - 自动保存和恢复用户的语言偏好 + * - 全局注入 - 在任何组件中都可以使用 $t 函数进行翻译 + * - 类型安全 - 提供 TypeScript 类型支持 + * + * ## 支持的语言 + * + * - zh: 简体中文 + * - en: English + * + * @module locales + * @author Art Design Pro Team + */ + +import { createI18n } from 'vue-i18n' +import type { I18n, I18nOptions } from 'vue-i18n' +import { LanguageEnum } from '@/enums/appEnum' +import { getSystemStorage } from '@/utils/storage' +import { StorageKeyManager } from '@/utils/storage/storage-key-manager' + +// 同步导入语言文件 +import enMessages from './langs/en.json' +import zhMessages from './langs/zh.json' + +/** + * 存储键管理器实例 + */ +const storageKeyManager = new StorageKeyManager() + +/** + * 语言消息对象 + */ +const messages = { + [LanguageEnum.EN]: enMessages, + [LanguageEnum.ZH]: zhMessages +} + +/** + * 语言选项列表 + * 用于语言切换下拉框 + */ +export const languageOptions = [ + { value: LanguageEnum.ZH, label: '简体中文' }, + { value: LanguageEnum.EN, label: 'English' } +] + +/** + * 从存储中获取语言设置 + * @returns 语言设置,如果获取失败则返回默认语言 + */ +const getDefaultLanguage = (): LanguageEnum => { + // 尝试从版本化的存储中获取语言设置 + 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) + } + + // 尝试从系统存储中获取语言设置 + 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 +} + +/** + * i18n 配置选项 + */ +const i18nOptions: I18nOptions = { + locale: getDefaultLanguage(), + legacy: false, + globalInjection: true, + fallbackLocale: LanguageEnum.ZH, + messages +} + +/** + * i18n 实例 + */ +const i18n: I18n = createI18n(i18nOptions) + +/** + * 翻译函数类型 + */ +interface Translation { + (key: string): string +} + +/** + * 全局翻译函数 + * 可在任何地方使用,无需导入 useI18n + */ +export const $t = i18n.global.t as Translation + +export default i18n diff --git a/adminSystem/src/locales/langs/en.json b/adminSystem/src/locales/langs/en.json new file mode 100644 index 0000000..51e196b --- /dev/null +++ b/adminSystem/src/locales/langs/en.json @@ -0,0 +1,296 @@ +{ + "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 diff --git a/adminSystem/src/locales/langs/zh.json b/adminSystem/src/locales/langs/zh.json new file mode 100644 index 0000000..77a23a5 --- /dev/null +++ b/adminSystem/src/locales/langs/zh.json @@ -0,0 +1,296 @@ +{ + "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 diff --git a/adminSystem/src/main.ts b/adminSystem/src/main.ts new file mode 100644 index 0000000..7df948a --- /dev/null +++ b/adminSystem/src/main.ts @@ -0,0 +1,25 @@ +import App from './App.vue' +import { createApp } from 'vue' +import { initStore } from './store' // Store +import { initRouter } from './router' // Router +import language from './locales' // 国际化 +import '@styles/core/tailwind.css' // tailwind +import '@styles/index.scss' // 样式 +import '@utils/sys/console.ts' // 控制台输出内容 +import { setupGlobDirectives } from './directives' +import { setupErrorHandle } from './utils/sys/error-handle' + +document.addEventListener( + 'touchstart', + function () {}, + { passive: false } +) + +const app = createApp(App) +initStore(app) +initRouter(app) +setupGlobDirectives(app) +setupErrorHandle(app) + +app.use(language) +app.mount('#app') \ No newline at end of file diff --git a/adminSystem/src/mock/temp/formData.ts b/adminSystem/src/mock/temp/formData.ts new file mode 100644 index 0000000..8aa7ae5 --- /dev/null +++ b/adminSystem/src/mock/temp/formData.ts @@ -0,0 +1,273 @@ +import avatar1 from '@/assets/images/avatar/avatar1.webp' +import avatar2 from '@/assets/images/avatar/avatar2.webp' +import avatar3 from '@/assets/images/avatar/avatar3.webp' +import avatar4 from '@/assets/images/avatar/avatar4.webp' +import avatar5 from '@/assets/images/avatar/avatar5.webp' +import avatar6 from '@/assets/images/avatar/avatar6.webp' +import avatar7 from '@/assets/images/avatar/avatar7.webp' +import avatar8 from '@/assets/images/avatar/avatar8.webp' +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 +} + +// 用户列表 +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 + } +] + +export interface Role { + 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 + } +] diff --git a/adminSystem/src/mock/upgrade/changeLog.ts b/adminSystem/src/mock/upgrade/changeLog.ts new file mode 100644 index 0000000..dd6b772 --- /dev/null +++ b/adminSystem/src/mock/upgrade/changeLog.ts @@ -0,0 +1,12 @@ +import { ref } from 'vue' + +interface UpgradeLog { + version: string // 版本号 + title: string // 更新标题 + date: string // 更新日期 + detail?: string[] // 更新内容 + requireReLogin?: boolean // 是否需要重新登录 + remark?: string // 备注 +} + +export const upgradeLogList = ref([]) diff --git a/adminSystem/src/plugins/echarts.ts b/adminSystem/src/plugins/echarts.ts new file mode 100644 index 0000000..4f56d89 --- /dev/null +++ b/adminSystem/src/plugins/echarts.ts @@ -0,0 +1,76 @@ +/** + * ECharts 插件配置 + * + * 按需导入 ECharts 图表和组件,减小打包体积。 + * 只注册项目中实际使用的图表类型和组件。 + * + * @module plugins/echarts + * @author Art Design Pro Team + */ + +// ECharts 按需导入配置 +import * as echarts from 'echarts/core' + +// 导入图表类型 +import { + BarChart, + LineChart, + PieChart, + ScatterChart, + RadarChart, + MapChart, + CandlestickChart +} from 'echarts/charts' + +// 导入组件 +import { + TitleComponent, + TooltipComponent, + GridComponent, + LegendComponent, + DataZoomComponent, + MarkPointComponent, + MarkLineComponent, + ToolboxComponent, + BrushComponent, + GeoComponent, + VisualMapComponent +} from 'echarts/components' + +// 导入渲染器 +import { CanvasRenderer } from 'echarts/renderers' + +// 注册必要的组件 +echarts.use([ + // 图表类型 + BarChart, + LineChart, + PieChart, + ScatterChart, + RadarChart, + MapChart, + CandlestickChart, + + // 组件 + TitleComponent, + TooltipComponent, + GridComponent, + LegendComponent, + DataZoomComponent, + MarkPointComponent, + MarkLineComponent, + ToolboxComponent, + BrushComponent, + GeoComponent, + VisualMapComponent, + + // 渲染器 + CanvasRenderer +]) + +// 导出 echarts 实例和类型 +export { echarts } +export type { EChartsOption, BarSeriesOption } from 'echarts' + +// 导出常用的图形工具 +export const graphic = echarts.graphic diff --git a/adminSystem/src/plugins/index.ts b/adminSystem/src/plugins/index.ts new file mode 100644 index 0000000..4536a86 --- /dev/null +++ b/adminSystem/src/plugins/index.ts @@ -0,0 +1,6 @@ +/** + * 插件统一导出 + * 集中管理第三方库的封装和配置 + */ + +export * from './echarts' diff --git a/adminSystem/src/router/core/ComponentLoader.ts b/adminSystem/src/router/core/ComponentLoader.ts new file mode 100644 index 0000000..8af3ce3 --- /dev/null +++ b/adminSystem/src/router/core/ComponentLoader.ts @@ -0,0 +1,82 @@ +/** + * 组件加载器 + * + * 负责动态加载 Vue 组件 + * + * @module router/core/ComponentLoader + * @author Art Design Pro Team + */ + +import { h } from 'vue' + +export class ComponentLoader { + private modules: Record Promise> + + constructor() { + // 动态导入 views 目录下所有 .vue 组件 + this.modules = import.meta.glob('../../views/**/*.vue') + } + + /** + * 加载组件 + */ + load(componentPath: string): () => Promise { + if (!componentPath) { + return this.createEmptyComponent() + } + + // 构建可能的路径 + const fullPath = `../../views${componentPath}.vue` + const fullPathWithIndex = `../../views${componentPath}/index.vue` + + // 先尝试直接路径,再尝试添加/index的路径 + const module = this.modules[fullPath] || this.modules[fullPathWithIndex] + + if (!module) { + console.error( + `[ComponentLoader] 未找到组件: ${componentPath},尝试过的路径: ${fullPath} 和 ${fullPathWithIndex}` + ) + return this.createErrorComponent(componentPath) + } + + return module + } + + /** + * 加载布局组件 + */ + loadLayout(): () => Promise { + return () => import('@/views/index/index.vue') + } + + /** + * 加载 iframe 组件 + */ + loadIframe(): () => Promise { + return () => import('@/views/outside/Iframe.vue') + } + + /** + * 创建空组件 + */ + 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}`) + } + }) + } +} diff --git a/adminSystem/src/router/core/IframeRouteManager.ts b/adminSystem/src/router/core/IframeRouteManager.ts new file mode 100644 index 0000000..c054ca1 --- /dev/null +++ b/adminSystem/src/router/core/IframeRouteManager.ts @@ -0,0 +1,78 @@ +/** + * Iframe 路由管理器 + * + * 负责管理 iframe 类型的路由 + * + * @module router/core/IframeRouteManager + * @author Art Design Pro Team + */ + +import type { AppRouteRecord } from '@/types/router' + +export class IframeRouteManager { + private static instance: IframeRouteManager + private iframeRoutes: AppRouteRecord[] = [] + + private constructor() {} + + 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 路由 + */ + getAll(): AppRouteRecord[] { + return this.iframeRoutes + } + + /** + * 根据路径查找 iframe 路由 + */ + findByPath(path: string): AppRouteRecord | undefined { + return this.iframeRoutes.find((route) => route.path === path) + } + + /** + * 清空所有 iframe 路由 + */ + clear(): void { + 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 = [] + } + } +} diff --git a/adminSystem/src/router/core/MenuProcessor.ts b/adminSystem/src/router/core/MenuProcessor.ts new file mode 100644 index 0000000..57bf183 --- /dev/null +++ b/adminSystem/src/router/core/MenuProcessor.ts @@ -0,0 +1,241 @@ +/** + * 菜单处理器 + * + * 负责菜单数据的获取、过滤和处理 + * + * @module router/core/MenuProcessor + * @author Art Design Pro Team + */ + +import type { AppRouteRecord } from '@/types/router' +import { useUserStore } from '@/store/modules/user' +import { useAppMode } from '@/hooks/core/useAppMode' +import { fetchGetMenuList } from '@/api/system-manage' +import { asyncRoutes } from '../routes/asyncRoutes' +import { RoutesAlias } from '../routesAlias' +import { formatMenuTitle } from '@/utils' + +export class MenuProcessor { + /** + * 获取菜单数据 + */ + async getMenuList(): Promise { + const { isFrontendMode } = useAppMode() + + let menuList: AppRouteRecord[] + if (isFrontendMode.value) { + menuList = await this.processFrontendMenu() + } else { + menuList = await this.processBackendMenu() + } + + // 在规范化路径之前,验证原始路径配置 + this.validateMenuPaths(menuList) + + // 规范化路径(将相对路径转换为完整路径) + return this.normalizeMenuPaths(menuList) + } + + /** + * 处理前端控制模式的菜单 + */ + private async processFrontendMenu(): Promise { + const userStore = useUserStore() + const roles = userStore.info?.roles + + let menuList = [...asyncRoutes] + + // 根据角色过滤菜单 + if (roles && roles.length > 0) { + menuList = this.filterMenuByRoles(menuList, roles) + } + + return this.filterEmptyMenus(menuList) + } + + /** + * 处理后端控制模式的菜单 + */ + 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)) + + if (hasPermission) { + const filteredItem = { ...item } + if (filteredItem.children?.length) { + filteredItem.children = this.filterMenuByRoles(filteredItem.children, roles) + } + acc.push(filteredItem) + } + + 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 + } + + // 如果有外链或 iframe,保留 + if (item.meta?.isIframe === true || item.meta?.link) { + return true + } + + // 如果有有效的 component,保留 + if (item.component && item.component !== '' && item.component !== RoutesAlias.Layout) { + return true + } + + // 其他情况过滤掉 + return false + }) + } + + /** + * 验证菜单列表是否有效 + */ + 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) + + // 递归处理子菜单 + const children = item.children?.length + ? this.normalizeMenuPaths(item.children, fullPath) + : item.children + + return { + ...item, + path: fullPath, + children + } + }) + } + + /** + * 验证菜单路径配置 + * 检测非一级菜单是否错误使用了 / 开头的路径 + */ + /** + * 验证菜单路径配置 + * 检测非一级菜单是否错误使用了 / 开头的路径 + */ + private validateMenuPaths(menuList: AppRouteRecord[], level = 1): void { + menuList.forEach((route) => { + if (!route.children?.length) return + + const parentName = String(route.name || route.path || '未知路由') + + route.children.forEach((child) => { + const childPath = child.path || '' + + // 跳过合法的绝对路径:外部链接和 iframe 路由 + if (this.isValidAbsolutePath(childPath)) return + + // 检测非法的绝对路径 + if (childPath.startsWith('/')) { + this.logPathError(child, childPath, parentName, level) + } + }) + + // 递归检查更深层级的子路由 + this.validateMenuPaths(route.children, level + 1) + }) + } + + /** + * 判断是否为合法的绝对路径 + */ + 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) + + 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 '' + + // 外部链接直接返回 + if (path.startsWith('http://') || path.startsWith('https://')) { + return path + } + + // 如果已经是绝对路径,直接返回 + if (path.startsWith('/')) { + return path + } + + // 拼接父路径和当前路径 + if (parentPath) { + // 移除父路径末尾的斜杠,移除子路径开头的斜杠,然后拼接 + const cleanParent = parentPath.replace(/\/$/, '') + const cleanChild = path.replace(/^\//, '') + return `${cleanParent}/${cleanChild}` + } + + // 没有父路径,添加前导斜杠 + return `/${path}` + } +} diff --git a/adminSystem/src/router/core/RoutePermissionValidator.ts b/adminSystem/src/router/core/RoutePermissionValidator.ts new file mode 100644 index 0000000..c33e663 --- /dev/null +++ b/adminSystem/src/router/core/RoutePermissionValidator.ts @@ -0,0 +1,119 @@ +/** + * 路由权限验证模块 + * + * 提供路由权限验证和路径检查功能 + * + * ## 主要功能 + * + * - 验证路径是否在用户菜单权限中 + * - 构建菜单路径集合(扁平化处理) + * - 支持动态路由参数匹配 + * - 路径前缀匹配 + * + * ## 使用场景 + * + * - 路由守卫中验证用户权限 + * - 动态路由注册后的权限检查 + * - 防止用户访问无权限的页面 + * + * @module router/core/RoutePermissionValidator + * @author Art Design Pro Team + */ + +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 + } + + // 构建路径集合 + const pathSet = this.buildMenuPathSet(menuList) + + // 检查路径是否在集合中(精确匹配或前缀匹配) + 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 + } + + for (const menuItem of menuList) { + // 跳过隐藏的菜单项 + if (menuItem.meta?.isHide || !menuItem.path) { + continue + } + + // 标准化路径并添加到集合 + const menuPath = menuItem.path.startsWith('/') ? menuItem.path : `/${menuItem.path}` + pathSet.add(menuPath) + + // 递归处理子菜单 + if (menuItem.children?.length) { + this.buildMenuPathSet(menuItem.children, 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 + } + + /** + * 验证并返回有效的路径 + * 如果目标路径无权限,返回首页路径 + * @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 } + } + + return { path: homePath, hasPermission: false } + } +} diff --git a/adminSystem/src/router/core/RouteRegistry.ts b/adminSystem/src/router/core/RouteRegistry.ts new file mode 100644 index 0000000..e1acb9e --- /dev/null +++ b/adminSystem/src/router/core/RouteRegistry.ts @@ -0,0 +1,90 @@ +/** + * 路由注册核心类 + * + * 负责动态路由的注册、验证和管理 + * + * @module router/core/RouteRegistry + * @author Art Design Pro Team + */ + +import type { Router, RouteRecordRaw } from 'vue-router' +import type { AppRouteRecord } from '@/types/router' +import { ComponentLoader } from './ComponentLoader' +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 + + 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 + } + + // 验证路由配置 + const validationResult = this.validator.validate(menuList) + if (!validationResult.valid) { + throw new Error(`路由配置验证失败: ${validationResult.errors.join(', ')}`) + } + + // 转换并注册路由 + 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) + } + }) + + this.removeRouteFns = removeRouteFns + this.registered = true + } + + /** + * 移除所有动态路由 + */ + unregister(): void { + this.removeRouteFns.forEach((fn) => fn()) + this.removeRouteFns = [] + this.registered = false + } + + /** + * 检查是否已注册 + */ + isRegistered(): boolean { + return this.registered + } + + /** + * 获取移除函数列表(用于 store 管理) + */ + getRemoveRouteFns(): (() => void)[] { + return this.removeRouteFns + } + + /** + * 标记为已注册(用于错误处理场景,避免重复请求) + */ + markAsRegistered(): void { + this.registered = true + } +} diff --git a/adminSystem/src/router/core/RouteTransformer.ts b/adminSystem/src/router/core/RouteTransformer.ts new file mode 100644 index 0000000..0f6900c --- /dev/null +++ b/adminSystem/src/router/core/RouteTransformer.ts @@ -0,0 +1,132 @@ +/** + * 路由转换器 + * + * 负责将菜单数据转换为 Vue Router 路由配置 + * + * @module router/core/RouteTransformer + * @author Art Design Pro Team + */ + +import type { RouteRecordRaw } from 'vue-router' +import type { AppRouteRecord } from '@/types/router' +import { ComponentLoader } from './ComponentLoader' +import { IframeRouteManager } from './IframeRouteManager' + +interface ConvertedRoute extends Omit { + id?: number + children?: ConvertedRoute[] + component?: RouteRecordRaw['component'] | (() => Promise) +} + +export class RouteTransformer { + private componentLoader: ComponentLoader + private iframeManager: IframeRouteManager + + constructor(componentLoader: ComponentLoader) { + this.componentLoader = componentLoader + this.iframeManager = IframeRouteManager.getInstance() + } + + /** + * 转换路由配置 + */ + transform(route: AppRouteRecord, depth = 0): ConvertedRoute { + const { component, children, ...routeConfig } = route + + // 基础路由配置 + 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 (children?.length) { + converted.children = children.map((child) => this.transform(child, depth + 1)) + } + + return converted + } + + /** + * 判断是否为一级路由(需要 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 = '' + + targetRoute.children = [ + { + ...sourceRoute, + component: this.componentLoader.loadIframe() + } as ConvertedRoute + ] + } else { + // 非顶级(嵌套)iframe:直接使用 Iframe.vue + targetRoute.component = this.componentLoader.loadIframe() + } + + // 记录 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 + + 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 extractFirstSegment(path: string): string { + const segments = path.split('/').filter(Boolean) + return segments.length > 0 ? `/${segments[0]}` : '/' + } +} diff --git a/adminSystem/src/router/core/RouteValidator.ts b/adminSystem/src/router/core/RouteValidator.ts new file mode 100644 index 0000000..f8e58fc --- /dev/null +++ b/adminSystem/src/router/core/RouteValidator.ts @@ -0,0 +1,187 @@ +/** + * 路由验证器 + * + * 负责验证路由配置的合法性 + * + * @module router/core/RouteValidator + * @author Art Design Pro Team + */ + +import type { AppRouteRecord } from '@/types/router' +import { RoutesAlias } from '../routesAlias' + +export interface ValidationResult { + valid: boolean + errors: string[] + warnings: string[] +} + +export class RouteValidator { + // 用于记录已经提示过的路由,避免重复提示 + private warnedRoutes = new Set() + + /** + * 验证路由配置 + */ + validate(routes: AppRouteRecord[]): ValidationResult { + const errors: string[] = [] + const warnings: string[] = [] + + // 检测重复路由 + this.checkDuplicates(routes, errors, warnings) + + // 检测组件配置 + this.checkComponents(routes, errors, warnings) + + // 检测嵌套菜单的 /index/index 配置 + this.checkNestedIndexComponent(routes) + + 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() + + 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.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) + } + }) + } + + 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 + + // 如果配置了 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 + } + + // 非一级菜单:如果既不是外链、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) + } + }) + } + + /** + * 检测嵌套菜单的 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) + } + }) + } + + /** + * 输出 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) + + 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' 或留空 ''(如果是目录菜单)` + ) + } + + /** + * 路径解析 + */ + private resolvePath(parent: string, child: string): string { + return [parent.replace(/\/$/, ''), child.replace(/^\//, '')].filter(Boolean).join('/') + } +} diff --git a/adminSystem/src/router/core/index.ts b/adminSystem/src/router/core/index.ts new file mode 100644 index 0000000..fcfecfc --- /dev/null +++ b/adminSystem/src/router/core/index.ts @@ -0,0 +1,14 @@ +/** + * 路由核心模块导出 + * + * @module router/core + * @author Art Design Pro Team + */ + +export { RouteRegistry } from './RouteRegistry' +export { ComponentLoader } from './ComponentLoader' +export { RouteValidator } from './RouteValidator' +export { RouteTransformer } from './RouteTransformer' +export { IframeRouteManager } from './IframeRouteManager' +export { MenuProcessor } from './MenuProcessor' +export { RoutePermissionValidator } from './RoutePermissionValidator' diff --git a/adminSystem/src/router/guards/afterEach.ts b/adminSystem/src/router/guards/afterEach.ts new file mode 100644 index 0000000..d60572d --- /dev/null +++ b/adminSystem/src/router/guards/afterEach.ts @@ -0,0 +1,34 @@ +import { nextTick } from 'vue' +import { useSettingStore } from '@/store/modules/setting' +import { Router } from 'vue-router' +import NProgress from 'nprogress' +import { useCommon } from '@/hooks/core/useCommon' +import { loadingService } from '@/utils/ui' +import { getPendingLoading, resetPendingLoading } from './beforeEach' + +/** 路由全局后置守卫 */ +export function setupAfterEachGuard(router: Router) { + const { scrollToTop } = useCommon() + + router.afterEach(() => { + scrollToTop() + + // 关闭进度条 + const settingStore = useSettingStore() + if (settingStore.showNprogress) { + NProgress.done() + // 确保进度条完全移除,避免残影 + setTimeout(() => { + NProgress.remove() + }, 600) + } + + // 关闭 loading 效果 + if (getPendingLoading()) { + nextTick(() => { + loadingService.hideLoading() + resetPendingLoading() + }) + } + }) +} diff --git a/adminSystem/src/router/guards/beforeEach.ts b/adminSystem/src/router/guards/beforeEach.ts new file mode 100644 index 0000000..0571e4f --- /dev/null +++ b/adminSystem/src/router/guards/beforeEach.ts @@ -0,0 +1,360 @@ +/** + * 路由全局前置守卫模块 + * + * 提供完整的路由导航守卫功能 + * + * ## 主要功能 + * + * - 登录状态验证和重定向 + * - 动态路由注册和权限控制 + * - 菜单数据获取和处理(前端/后端模式) + * - 用户信息获取和缓存 + * - 页面标题设置 + * - 工作标签页管理 + * - 进度条和加载动画控制 + * - 静态路由识别和处理 + * - 错误处理和异常跳转 + * + * ## 使用场景 + * + * - 路由跳转前的权限验证 + * - 动态菜单加载和路由注册 + * - 用户登录状态管理 + * - 页面访问控制 + * - 路由级别的加载状态管理 + * + * ## 工作流程 + * + * 1. 检查登录状态,未登录跳转到登录页 + * 2. 首次访问时获取用户信息和菜单数据 + * 3. 根据权限动态注册路由 + * 4. 设置页面标题和工作标签页 + * 5. 处理根路径重定向到首页 + * 6. 未匹配路由跳转到 404 页面 + * + * @module router/guards/beforeEach + * @author Art Design Pro Team + */ +import type { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router' +import { nextTick } from 'vue' +import NProgress from 'nprogress' +import { useSettingStore } from '@/store/modules/setting' +import { useUserStore } from '@/store/modules/user' +import { useMenuStore } from '@/store/modules/menu' +import { setWorktab } from '@/utils/navigation' +import { setPageTitle } from '@/utils/router' +import { RoutesAlias } from '../routesAlias' +import { staticRoutes } from '../routes/staticRoutes' +import { loadingService } from '@/utils/ui' +import { useCommon } from '@/hooks/core/useCommon' +import { useWorktabStore } from '@/store/modules/worktab' +import { fetchGetUserInfo } from '@/api/auth' +import { ApiStatus } from '@/utils/http/status' +import { isHttpError } from '@/utils/http/error' +import { RouteRegistry, MenuProcessor, IframeRouteManager, RoutePermissionValidator } from '../core' + +// 路由注册器实例 +let routeRegistry: RouteRegistry | null = null + +// 菜单处理器实例 +const menuProcessor = new MenuProcessor() + +// 跟踪是否需要关闭 loading +let pendingLoading = false + +/** + * 获取 pendingLoading 状态 + */ +export function getPendingLoading(): boolean { + return pendingLoading +} + +/** + * 重置 pendingLoading 状态 + */ +export function resetPendingLoading(): void { + pendingLoading = false +} + +/** + * 设置路由全局前置守卫 + */ +export function setupBeforeEachGuard(router: Router): void { + // 初始化路由注册器 + 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' }) + } + } + ) +} + +/** + * 关闭 loading 效果 + */ +function closeLoading(): void { + if (pendingLoading) { + nextTick(() => { + loadingService.hideLoading() + pendingLoading = false + }) + } +} + +/** + * 处理路由守卫逻辑 + */ +async function handleRouteGuard( + to: RouteLocationNormalized, + from: RouteLocationNormalized, + next: NavigationGuardNext, + router: Router +): Promise { + const settingStore = useSettingStore() + const userStore = useUserStore() + + // 启动进度条 + if (settingStore.showNprogress) { + NProgress.start() + } + + // 1. 检查登录状态 + if (!handleLoginStatus(to, userStore, next)) { + return + } + + // 2. 处理动态路由注册 + if (!routeRegistry?.isRegistered() && userStore.isLogin) { + await handleDynamicRoutes(to, next, router) + return + } + + // 3. 处理根路径重定向 + if (handleRootPathRedirect(to, next)) { + return + } + + // 4. 处理已匹配的路由 + if (to.matched.length > 0) { + setWorktab(to) + setPageTitle(to) + next() + return + } + + // 5. 未匹配到路由,跳转到 404 + next({ name: 'Exception404' }) +} + +/** + * 处理登录状态 + * @returns true 表示可以继续,false 表示已处理跳转 + */ +function handleLoginStatus( + to: RouteLocationNormalized, + userStore: ReturnType, + next: NavigationGuardNext +): boolean { + // 已登录或访问登录页或静态路由,直接放行 + if (userStore.isLogin || to.path === RoutesAlias.Login || isStaticRoute(to.path)) { + return true + } + + // 未登录且访问需要权限的页面,跳转到登录页并携带 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}$`) + + if (regex.test(targetPath)) { + return true + } + if (route.children && route.children.length > 0) { + return checkRoute(route.children, targetPath) + } + return false + }) + } + + return checkRoute(staticRoutes, path) +} + +/** + * 处理动态路由注册 + */ +async function handleDynamicRoutes( + to: RouteLocationNormalized, + next: NavigationGuardNext, + router: Router +): Promise { + // 显示 loading + pendingLoading = true + loadingService.showLoading() + + try { + // 1. 获取用户信息 + await fetchUserInfo() + + // 2. 获取菜单数据 + const menuList = await menuProcessor.getMenuList() + + // 3. 验证菜单数据 + if (!menuProcessor.validateMenuList(menuList)) { + throw new Error('获取菜单列表失败,请重新登录') + } + + // 4. 注册动态路由 + routeRegistry?.register(menuList) + + // 5. 保存菜单数据到 store + const menuStore = useMenuStore() + menuStore.setMenuList(menuList) + menuStore.addRemoveRouteFns(routeRegistry?.getRemoveRouteFns() || []) + + // 6. 保存 iframe 路由 + IframeRouteManager.getInstance().save() + + // 7. 验证工作标签页 + useWorktabStore().validateWorktabs(router) + + // 8. 验证目标路径权限 + const { homePath } = useCommon() + const { path: validatedPath, hasPermission } = RoutePermissionValidator.validatePath( + to.path, + menuList, + homePath.value || '/' + ) + + // 9. 重新导航到目标路由 + if (!hasPermission) { + // 无权限访问,跳转到首页 + closeLoading() + + // 输出警告信息 + 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) + + // 401 错误:axios 拦截器已处理退出登录,取消当前导航 + if (isUnauthorizedError(error)) { + closeLoading() + next(false) + return + } + + // 404 错误:接口不存在,标记路由已注册避免重复请求 + if (isNotFoundError(error)) { + console.error('[RouteGuard] 接口返回 404,请检查后端接口配置') + routeRegistry?.markAsRegistered() + closeLoading() + next({ name: 'Exception404' }) + return + } + + // 其他错误:跳转到 500 页面 + next({ name: 'Exception500' }) + } +} + +/** + * 获取用户信息 + * + * 注意:每次动态路由注册时都会重新获取用户信息,确保数据最新 + * 这样可以避免以下问题: + * 1. 用户信息过期但仍使用 localStorage 中的旧数据 + * 2. 权限变更后不能及时更新 + * 3. 用户信息在后台被修改后前端不同步 + */ +async function fetchUserInfo(): Promise { + const userStore = useUserStore() + const data = await fetchGetUserInfo() + userStore.setUserInfo(data) + // 检查并清理工作台标签页(如果是不同用户登录) + userStore.checkAndClearWorktabs() +} + +/** + * 重置路由相关状态 + */ +export function resetRouterState(delay: number): void { + setTimeout(() => { + routeRegistry?.unregister() + IframeRouteManager.getInstance().clear() + + const menuStore = useMenuStore() + menuStore.removeAllDynamicRoutes() + menuStore.setMenuList([]) + }, delay) +} + +/** + * 处理根路径重定向到首页 + * @returns true 表示已处理跳转,false 表示无需跳转 + */ +function handleRootPathRedirect(to: RouteLocationNormalized, next: NavigationGuardNext): boolean { + if (to.path !== '/') { + return false + } + + const { homePath } = useCommon() + if (homePath.value && homePath.value !== '/') { + next({ path: homePath.value, replace: true }) + return true + } + + return false +} + +/** + * 判断是否为未授权错误(401) + */ +function isUnauthorizedError(error: unknown): boolean { + return isHttpError(error) && error.code === ApiStatus.unauthorized +} + +/** + * 判断是否为 404 错误 + */ +function isNotFoundError(error: unknown): boolean { + return isHttpError(error) && error.code === ApiStatus.notFound +} diff --git a/adminSystem/src/router/index.ts b/adminSystem/src/router/index.ts new file mode 100644 index 0000000..286ae58 --- /dev/null +++ b/adminSystem/src/router/index.ts @@ -0,0 +1,23 @@ +import type { App } from 'vue' +import { createRouter, createWebHashHistory } from 'vue-router' +import { staticRoutes } from './routes/staticRoutes' +import { configureNProgress } from '@/utils/router' +import { setupBeforeEachGuard } from './guards/beforeEach' +import { setupAfterEachGuard } from './guards/afterEach' + +// 创建路由实例 +export const router = createRouter({ + history: createWebHashHistory(), + routes: staticRoutes // 静态路由 +}) + +// 初始化路由 +export function initRouter(app: App): void { + configureNProgress() // 顶部进度条 + setupBeforeEachGuard(router) // 路由前置守卫 + setupAfterEachGuard(router) // 路由后置守卫 + app.use(router) +} + +// 主页路径,默认使用菜单第一个有效路径,配置后使用此路径 +export const HOME_PAGE_PATH = '' diff --git a/adminSystem/src/router/modules/dashboard.ts b/adminSystem/src/router/modules/dashboard.ts new file mode 100644 index 0000000..5f9c3e9 --- /dev/null +++ b/adminSystem/src/router/modules/dashboard.ts @@ -0,0 +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 + } + } + ] +} diff --git a/adminSystem/src/router/modules/exception.ts b/adminSystem/src/router/modules/exception.ts new file mode 100644 index 0000000..07c5604 --- /dev/null +++ b/adminSystem/src/router/modules/exception.ts @@ -0,0 +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 + } + } + ] +} diff --git a/adminSystem/src/router/modules/index.ts b/adminSystem/src/router/modules/index.ts new file mode 100644 index 0000000..deff162 --- /dev/null +++ b/adminSystem/src/router/modules/index.ts @@ -0,0 +1,15 @@ +import { AppRouteRecord } from '@/types/router' +import { dashboardRoutes } from './dashboard' +import { systemRoutes } from './system' +import { resultRoutes } from './result' +import { exceptionRoutes } from './exception' + +/** + * 导出所有模块化路由 + */ +export const routeModules: AppRouteRecord[] = [ + dashboardRoutes, + systemRoutes, + resultRoutes, + exceptionRoutes +] diff --git a/adminSystem/src/router/modules/result.ts b/adminSystem/src/router/modules/result.ts new file mode 100644 index 0000000..575a2f7 --- /dev/null +++ b/adminSystem/src/router/modules/result.ts @@ -0,0 +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 + } + } + ] +} diff --git a/adminSystem/src/router/modules/system.ts b/adminSystem/src/router/modules/system.ts new file mode 100644 index 0000000..16df585 --- /dev/null +++ b/adminSystem/src/router/modules/system.ts @@ -0,0 +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' } + ] + } + } + ] +} diff --git a/adminSystem/src/router/routes/asyncRoutes.ts b/adminSystem/src/router/routes/asyncRoutes.ts new file mode 100644 index 0000000..ccf1201 --- /dev/null +++ b/adminSystem/src/router/routes/asyncRoutes.ts @@ -0,0 +1,9 @@ +// 权限文档:https://www.artd.pro/docs/zh/guide/in-depth/permission.html +import { AppRouteRecord } from '@/types/router' +import { routeModules } from '../modules' + +/** + * 动态路由(需要权限才能访问的路由) + * 用于渲染菜单以及根据菜单权限动态加载路由,如果没有权限无法访问 + */ +export const asyncRoutes: AppRouteRecord[] = routeModules diff --git a/adminSystem/src/router/routes/staticRoutes.ts b/adminSystem/src/router/routes/staticRoutes.ts new file mode 100644 index 0000000..334d0c2 --- /dev/null +++ b/adminSystem/src/router/routes/staticRoutes.ts @@ -0,0 +1,72 @@ +import { AppRouteRecordRaw } from '@/utils/router' + +/** + * 静态路由配置(不需要权限就能访问的路由) + * + * 属性说明: + * isHideTab: true 表示不在标签页中显示 + * + * 注意事项: + * 1、path、name 不要和动态路由冲突,否则会导致路由冲突无法访问 + * 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' } + } + ] + } +] diff --git a/adminSystem/src/router/routesAlias.ts b/adminSystem/src/router/routesAlias.ts new file mode 100644 index 0000000..2af1c68 --- /dev/null +++ b/adminSystem/src/router/routesAlias.ts @@ -0,0 +1,8 @@ +/** + * 公共路由别名 + # 存放系统级公共路由路径,如布局容器、登录页等 + */ +export enum RoutesAlias { + Layout = '/index/index', // 布局容器 + Login = '/auth/login' // 登录页 +} diff --git a/adminSystem/src/store/index.ts b/adminSystem/src/store/index.ts new file mode 100644 index 0000000..b485999 --- /dev/null +++ b/adminSystem/src/store/index.ts @@ -0,0 +1,52 @@ +/** + * Pinia Store 配置模块 + * + * 提供全局状态管理的初始化和配置 + * + * ## 主要功能 + * + * - Pinia Store 实例创建 + * - 持久化插件配置(pinia-plugin-persistedstate) + * - 版本化存储键管理 + * - 自动数据迁移(跨版本) + * - LocalStorage 序列化配置 + * - Store 初始化函数 + * + * ## 持久化策略 + * + * - 使用 StorageKeyManager 生成版本化的存储键 + * - 格式:sys-v{version}-{storeId} + * - 自动迁移旧版本数据到当前版本 + * - 使用 localStorage 作为存储介质 + * + * @module store/index + * @author Art Design Pro Team + */ +import type { App } from 'vue' +import { createPinia } from 'pinia' +import { createPersistedState } from 'pinia-plugin-persistedstate' +import { StorageKeyManager } from '@/utils/storage/storage-key-manager' + +export const store = createPinia() + +// 创建存储键管理器实例 +const storageKeyManager = new StorageKeyManager() + +// 配置持久化插件 +store.use( + 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) +} diff --git a/adminSystem/src/store/modules/menu.ts b/adminSystem/src/store/modules/menu.ts new file mode 100644 index 0000000..85d13da --- /dev/null +++ b/adminSystem/src/store/modules/menu.ts @@ -0,0 +1,109 @@ +/** + * 菜单状态管理模块 + * + * 提供菜单数据和动态路由的状态管理 + * + * ## 主要功能 + * + * - 菜单列表存储和管理 + * - 首页路径配置 + * - 动态路由注册和移除 + * - 路由移除函数管理 + * - 菜单宽度配置 + * + * ## 使用场景 + * + * - 动态菜单加载和渲染 + * - 路由权限控制 + * - 首页路径动态设置 + * - 登出时清理动态路由 + * + * ## 工作流程 + * + * 1. 获取菜单数据(前端/后端模式) + * 2. 设置菜单列表和首页路径 + * 3. 注册动态路由并保存移除函数 + * 4. 登出时调用移除函数清理路由 + * + * @module store/modules/menu + * @author Art Design Pro Team + */ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { AppRouteRecord } from '@/types/router' +import { getFirstMenuPath } from '@/utils' +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)[]>([]) + + /** + * 设置菜单列表 + * @param list 菜单路由记录数组 + */ + const setMenuList = (list: AppRouteRecord[]) => { + menuList.value = list + setHomePath(HOME_PAGE_PATH || getFirstMenuPath(list)) + } + + /** + * 获取首页路径 + * @returns 首页路径字符串 + */ + const getHomePath = () => homePath.value + + /** + * 设置主页路径 + * @param path 主页路径 + */ + const setHomePath = (path: string) => { + homePath.value = path + } + + /** + * 添加路由移除函数 + * @param fns 要添加的路由移除函数数组 + */ + const addRemoveRouteFns = (fns: (() => void)[]) => { + removeRouteFns.value.push(...fns) + } + + /** + * 移除所有动态路由 + * 执行所有存储的路由移除函数并清空数组 + */ + const removeAllDynamicRoutes = () => { + removeRouteFns.value.forEach((fn) => fn()) + removeRouteFns.value = [] + } + + /** + * 清空路由移除函数数组 + */ + const clearRemoveRouteFns = () => { + removeRouteFns.value = [] + } + + return { + menuList, + menuWidth, + removeRouteFns, + setMenuList, + getHomePath, + setHomePath, + addRemoveRouteFns, + removeAllDynamicRoutes, + clearRemoveRouteFns + } +}) diff --git a/adminSystem/src/store/modules/setting.ts b/adminSystem/src/store/modules/setting.ts new file mode 100644 index 0000000..2878259 --- /dev/null +++ b/adminSystem/src/store/modules/setting.ts @@ -0,0 +1,450 @@ +/** + * 系统设置状态管理模块 + * + * 提供完整的系统设置状态管理 + * + * ## 主要功能 + * + * - 菜单布局配置(左侧、顶部、混合、双栏) + * - 主题管理(亮色、暗色、自动) + * - 菜单主题样式配置 + * - 界面显示开关(面包屑、标签页、语言切换等) + * - 功能开关(手风琴模式、色弱模式、水印等) + * - 样式配置(边框、圆角、容器宽度、页面过渡) + * - 节日功能配置 + * - Element Plus 主题色动态设置 + * + * ## 使用场景 + * + * - 设置面板配置管理 + * - 主题切换和样式定制 + * - 界面功能开关控制 + * - 用户偏好设置持久化 + * + * ## 持久化 + * + * - 使用 localStorage 存储 + * - 存储键:sys-v{version}-setting + * - 支持跨版本数据迁移 + * + * @module store/modules/setting + * @author Art Design Pro Team + */ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { MenuThemeType } from '@/types/store' +import AppConfig from '@/config' +import { SystemThemeEnum, MenuThemeEnum, MenuTypeEnum, ContainerWidthEnum } from '@/enums/appEnum' +import { setElementThemeColor } from '@/utils/ui' +import { useCeremony } from '@/hooks/core/useCeremony' +import { StorageConfig } from '@/utils' +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) + + // 主题相关设置 + /** 系统主题类型 */ + 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 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 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 isDark = computed((): boolean => { + return systemThemeType.value === SystemThemeEnum.DARK + }) + + /** + * 获取菜单展开宽度 + */ + 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 isShowFireworks = computed((): boolean => { + return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true + }) + + /** + * 切换菜单布局 + * @param type 菜单类型 + */ + const switchMenuLayouts = (type: MenuTypeEnum) => { + menuType.value = type + } + + /** + * 设置菜单展开宽度 + * @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 菜单主题 + */ + const switchMenuStyles = (theme: MenuThemeEnum) => { + menuThemeType.value = theme + } + + /** + * 设置Element Plus主题颜色 + * @param theme 主题颜色 + */ + const setElementTheme = (theme: string) => { + systemThemeColor.value = theme + setElementThemeColor(theme) + } + + /** + * 切换边框模式 + */ + const setBorderMode = () => { + boxBorderMode.value = !boxBorderMode.value + } + + /** + * 设置容器宽度 + * @param width 容器宽度枚举值 + */ + const setContainerWidth = (width: ContainerWidthEnum) => { + containerWidth.value = width + } + + /** + * 切换唯一展开模式 + */ + const setUniqueOpened = () => { + uniqueOpened.value = !uniqueOpened.value + } + + /** + * 切换菜单按钮显示 + */ + const setButton = () => { + showMenuButton.value = !showMenuButton.value + } + + /** + * 切换快速入口显示 + */ + const setFastEnter = () => { + showFastEnter.value = !showFastEnter.value + } + + /** + * 切换自动关闭 + */ + const setAutoClose = () => { + autoClose.value = !autoClose.value + } + + /** + * 切换刷新按钮显示 + */ + const setShowRefreshButton = () => { + showRefreshButton.value = !showRefreshButton.value + } + + /** + * 切换面包屑显示 + */ + const setCrumbs = () => { + showCrumbs.value = !showCrumbs.value + } + + /** + * 设置工作台标签显示 + * @param show 是否显示 + */ + const setWorkTab = (show: boolean) => { + showWorkTab.value = show + } + + /** + * 切换语言切换显示 + */ + const setLanguage = () => { + showLanguage.value = !showLanguage.value + } + + /** + * 切换进度条显示 + */ + const setNprogress = () => { + showNprogress.value = !showNprogress.value + } + + /** + * 切换色弱模式 + */ + const setColorWeak = () => { + colorWeak.value = !colorWeak.value + } + + /** + * 隐藏设置引导 + */ + const hideSettingGuide = () => { + showSettingGuide.value = false + } + + /** + * 显示设置引导 + */ + const openSettingGuide = () => { + showSettingGuide.value = true + } + + /** + * 设置页面过渡效果 + * @param transition 过渡效果名称 + */ + const setPageTransition = (transition: string) => { + pageTransition.value = transition + } + + /** + * 设置标签页样式 + * @param style 样式名称 + */ + const setTabStyle = (style: string) => { + tabStyle.value = style + } + + /** + * 设置菜单展开状态 + * @param open 是否展开 + */ + const setMenuOpen = (open: boolean) => { + menuOpen.value = open + } + + /** + * 刷新页面 + */ + const reload = () => { + refresh.value = !refresh.value + } + + /** + * 设置水印显示 + * @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 isLoad 是否已加载 + */ + const setholidayFireworksLoaded = (isLoad: boolean) => { + holidayFireworksLoaded.value = isLoad + } + + /** + * 设置节日文本显示 + * @param show 是否显示 + */ + const setShowFestivalText = (show: boolean) => { + showFestivalText.value = show + } + + const setFestivalDate = (date: string) => { + festivalDate.value = date + } + + 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 + } + } +) diff --git a/adminSystem/src/store/modules/table.ts b/adminSystem/src/store/modules/table.ts new file mode 100644 index 0000000..094c310 --- /dev/null +++ b/adminSystem/src/store/modules/table.ts @@ -0,0 +1,97 @@ +/** + * 表格状态管理模块 + * + * 提供表格显示配置的状态管理 + * + * ## 主要功能 + * + * - 表格尺寸配置(紧凑、默认、宽松) + * - 斑马纹显示开关 + * - 边框显示开关 + * - 表头背景显示开关 + * - 全屏模式开关 + * + * ## 使用场景 + * - 表格组件样式配置 + * - 用户表格偏好设置 + * - 表格工具栏功能控制 + * + * ## 持久化 + * + * - 使用 localStorage 存储 + * - 存储键:sys-v{version}-table + * - 用户配置跨页面保持 + * + * @module store/modules/table + * @author Art Design Pro Team + */ +import { defineStore } from 'pinia' +import { ref } from 'vue' +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) + + // 是否全屏 + const isFullScreen = ref(false) + + /** + * 设置表格大小 + * @param size 表格大小枚举值 + */ + const setTableSize = (size: TableSizeEnum) => (tableSize.value = size) + + /** + * 设置斑马纹显示状态 + * @param value 是否显示斑马纹 + */ + const setIsZebra = (value: boolean) => (isZebra.value = value) + + /** + * 设置表格边框显示状态 + * @param value 是否显示边框 + */ + const setIsBorder = (value: boolean) => (isBorder.value = value) + + /** + * 设置表头背景显示状态 + * @param value 是否显示表头背景 + */ + const setIsHeaderBackground = (value: boolean) => (isHeaderBackground.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 + } + } +) diff --git a/adminSystem/src/store/modules/user.ts b/adminSystem/src/store/modules/user.ts new file mode 100644 index 0000000..08f7684 --- /dev/null +++ b/adminSystem/src/store/modules/user.ts @@ -0,0 +1,235 @@ +/** + * 用户状态管理模块 + * + * 提供用户相关的状态管理 + * + * ## 主要功能 + * + * - 用户登录状态管理 + * - 用户信息存储 + * - 访问令牌和刷新令牌管理 + * - 语言设置 + * - 搜索历史记录 + * - 锁屏状态和密码管理 + * - 登出清理逻辑 + * + * ## 使用场景 + * + * - 用户登录和认证 + * - 权限验证 + * - 个人信息展示 + * - 多语言切换 + * - 锁屏功能 + * - 搜索历史管理 + * + * ## 持久化 + * + * - 使用 localStorage 存储 + * - 存储键:sys-v{version}-user + * - 登出时自动清理 + * + * @module store/modules/user + * @author Art Design Pro Team + */ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { LanguageEnum } from '@/enums/appEnum' +import { router } from '@/router' +import { useSettingStore } from './setting' +import { useWorktabStore } from './worktab' +import { AppRouteRecord } from '@/types/router' +import { setPageTitle } from '@/utils/router' +import { resetRouterState } from '@/router/guards/beforeEach' +import { useMenuStore } from './menu' +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('') + + // 计算属性:获取用户信息 + 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 status 登录状态 + */ + const setLoginStatus = (status: boolean) => { + isLogin.value = status + } + + /** + * 设置语言 + * @param lang 语言枚举值 + */ + const setLanguage = (lang: LanguageEnum) => { + setPageTitle(router.currentRoute.value) + language.value = lang + } + + /** + * 设置搜索历史 + * @param list 搜索历史列表 + */ + const setSearchHistory = (list: AppRouteRecord[]) => { + searchHistory.value = list + } + + /** + * 设置锁屏状态 + * @param status 锁屏状态 + */ + const setLockStatus = (status: boolean) => { + isLock.value = status + } + + /** + * 设置锁屏密码 + * @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 + } + } + + /** + * 退出登录 + * 清空所有用户相关状态并跳转到登录页 + * 如果是同一账号重新登录,保留工作台标签页 + */ + 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 + }) + } + + /** + * 检查并清理工作台标签页 + * 如果不是同一用户登录,清空工作台标签页 + * 应在登录成功后调用 + */ + const checkAndClearWorktabs = () => { + const lastUserId = localStorage.getItem(StorageConfig.LAST_USER_ID_KEY) + const currentUserId = info.value.userId + + // 无法获取当前用户 ID,跳过检查 + if (!currentUserId) return + + // 首次登录或缓存已清除,保留现有标签页 + if (!lastUserId) { + return + } + + // 不同用户登录,清空工作台标签页 + if (String(currentUserId) !== lastUserId) { + const worktabStore = useWorktabStore() + worktabStore.opened = [] + worktabStore.keepAliveExclude = [] + } + + // 清除临时存储 + 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 + } + } +) diff --git a/adminSystem/src/store/modules/worktab.ts b/adminSystem/src/store/modules/worktab.ts new file mode 100644 index 0000000..caa0d90 --- /dev/null +++ b/adminSystem/src/store/modules/worktab.ts @@ -0,0 +1,568 @@ +/** + * 工作标签页状态管理模块 + * + * 提供多标签页功能的完整状态管理 + * + * ## 主要功能 + * + * - 标签页打开和关闭 + * - 标签页固定和取消固定 + * - 批量关闭(左侧、右侧、其他、全部) + * - 标签页缓存管理(KeepAlive) + * - 标签页标题自定义 + * - 标签页路由验证 + * - 动态路由参数处理 + * + * ## 使用场景 + * + * - 多标签页导航 + * - 页面缓存控制 + * - 标签页右键菜单 + * - 固定常用页面 + * - 批量关闭标签 + * + * ## 核心特性 + * + * - 智能标签页复用(同路由名称复用) + * - 固定标签页保护(不可关闭) + * - KeepAlive 缓存排除管理 + * - 路由有效性验证 + * - 首页自动保留 + * + * ## 持久化 + * - 使用 localStorage 存储 + * - 存储键:sys-v{version}-worktab + * - 刷新页面保持标签状态 + * + * @module store/modules/worktab + * @author Art Design Pro Team + */ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { router } from '@/router' +import { LocationQueryRaw, Router } from 'vue-router' +import { WorkTab } from '@/types' +import { useCommon } from '@/hooks/core/useCommon' + +interface WorktabState { + 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 + } + } +) diff --git a/adminSystem/src/types/api/api.d.ts b/adminSystem/src/types/api/api.d.ts new file mode 100644 index 0000000..fd3abbb --- /dev/null +++ b/adminSystem/src/types/api/api.d.ts @@ -0,0 +1,135 @@ +/** + * API 接口类型定义模块 + * + * 提供所有后端接口的类型定义 + * + * ## 主要功能 + * + * - 通用类型(分页参数、响应结构等) + * - 认证类型(登录、用户信息等) + * - 系统管理类型(用户、角色等) + * - 全局命名空间声明 + * + * ## 使用场景 + * + * - API 请求参数类型约束 + * - API 响应数据类型定义 + * - 接口文档类型同步 + * + * ## 注意事项 + * + * - 在 .vue 文件使用需要在 eslint.config.mjs 中配置 globals: { Api: 'readonly' } + * - 使用全局命名空间,无需导入即可使用 + * + * ## 使用方式 + * + * ```typescript + * const params: Api.Auth.LoginParams = { userName: 'admin', password: '123456' } + * const response: Api.Auth.UserInfo = await fetchUserInfo() + * ``` + * + * @module types/api/api + * @author Art Design Pro Team + */ + +declare namespace Api { + /** 通用类型 */ + namespace Common { + /** 分页参数 */ + interface PaginationParams { + /** 当前页码 */ + current: number + /** 每页条数 */ + size: number + /** 总条数 */ + total: number + } + + /** 通用搜索参数 */ + type CommonSearchParams = Pick + + /** 分页响应基础结构 */ + interface PaginatedResponse { + records: T[] + current: number + size: number + total: number + } + + /** 启用状态 */ + type EnableStatus = '1' | '2' + } + + /** 认证类型 */ + namespace Auth { + /** 登录参数 */ + interface LoginParams { + userName: string + password: string + } + + /** 登录响应 */ + interface LoginResponse { + token: string + refreshToken: string + } + + /** 用户信息 */ + interface UserInfo { + buttons: string[] + roles: string[] + userId: number + userName: string + email: string + avatar?: string + } + } + + /** 系统管理类型 */ + 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 + } + + /** 用户搜索参数 */ + type UserSearchParams = Partial< + Pick & + Api.Common.CommonSearchParams + > + + /** 角色列表 */ + type RoleList = Api.Common.PaginatedResponse + + /** 角色列表项 */ + interface RoleListItem { + roleId: number + roleName: string + roleCode: string + description: string + enabled: boolean + createTime: string + } + + /** 角色搜索参数 */ + type RoleSearchParams = Partial< + Pick & + Api.Common.CommonSearchParams + > + } +} diff --git a/adminSystem/src/types/common/index.ts b/adminSystem/src/types/common/index.ts new file mode 100644 index 0000000..7e751d1 --- /dev/null +++ b/adminSystem/src/types/common/index.ts @@ -0,0 +1,95 @@ +/** + * 通用类型定义模块 + * + * 提供项目中常用的通用类型定义 + * + * ## 主要功能 + * + * - 状态类型(启用/禁用) + * - 性别类型 + * - 排序方向类型 + * - 操作类型(增删改查) + * - 记录类型(键值对) + * - 时间范围类型 + * - 文件信息类型 + * - 坐标和尺寸类型 + * - 响应式断点类型 + * - 主题和语言类型 + * - 环境和弹窗类型 + * + * ## 使用场景 + * + * - 通用数据结构定义 + * - 类型约束和提示 + * - 减少重复类型定义 + * + * @module types/common/index + * @author Art Design Pro Team + */ + +// 导出响应类型 +export * from './response' + +// 状态类型 +export type Status = 0 | 1 // 0: 禁用, 1: 启用 + +// 性别类型 +export type Gender = 'male' | 'female' | 'unknown' + +// 排序方向 +export type SortOrder = 'ascending' | 'descending' + +// 操作类型 +export type ActionType = 'create' | 'update' | 'delete' | 'view' + +// 可选的记录类型 +export type Recordable = Record + +// 键值对类型 +export type KeyValue = { + key: string + value: T + label?: string +} + +// 时间范围类型 +export interface TimeRange { + startTime: string + endTime: string +} + +// 文件类型 +export interface FileInfo { + name: string + url: string + size: number + type: string + lastModified?: number +} + +// 坐标类型 +export interface Position { + x: number + y: number +} + +// 尺寸类型 +export interface Size { + width: number + height: number +} + +// 响应式断点类型 +export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' + +// 主题类型 +export type ThemeMode = 'light' | 'dark' | 'auto' + +// 语言类型 +export type Language = 'zh-CN' | 'en-US' + +// 环境类型 +export type Environment = 'development' | 'production' | 'test' + +// 弹窗类型 +export type DialogType = 'add' | 'edit' diff --git a/adminSystem/src/types/common/response.ts b/adminSystem/src/types/common/response.ts new file mode 100644 index 0000000..4a5fdab --- /dev/null +++ b/adminSystem/src/types/common/response.ts @@ -0,0 +1,30 @@ +/** + * API 响应类型定义模块 + * + * 提供统一的 API 响应结构类型定义 + * + * ## 主要功能 + * + * - 基础响应结构定义 + * - 泛型支持(适配不同数据类型) + * - 统一的响应格式约束 + * + * ## 使用场景 + * + * - API 请求响应类型约束 + * - 接口数据类型定义 + * - 响应数据解析 + * + * @module types/common/response + * @author Art Design Pro Team + */ + +/** 基础 API 响应结构 */ +export interface BaseResponse { + /** 状态码 */ + code: number + /** 消息 */ + msg: string + /** 数据 */ + data: T +} diff --git a/adminSystem/src/types/component/chart.ts b/adminSystem/src/types/component/chart.ts new file mode 100644 index 0000000..c3225c9 --- /dev/null +++ b/adminSystem/src/types/component/chart.ts @@ -0,0 +1,324 @@ +/** + * 图表组件类型定义模块 + * + * 提供 ECharts 图表组件的完整类型定义 + * + * ## 主要功能 + * + * - 基础图表配置类型 + * - 柱状图类型定义 + * - 折线图类型定义 + * - 饼图/环形图类型定义 + * - 雷达图类型定义 + * - K线图类型定义 + * - 散点图类型定义 + * - 地图图表类型定义 + * - 双向堆叠柱状图类型定义 + * - 图表主题配置类型 + * - 图表事件回调类型 + * + * ## 使用场景 + * + * - 图表组件 Props 类型约束 + * - 图表配置类型定义 + * - 图表数据结构定义 + * - 图表事件处理 + * + * @module types/component/chart + * @author Art Design Pro Team + */ +import type { EChartsOption } from '@/plugins/echarts' + +// 图例位置类型 +export type LegendPosition = 'bottom' | 'top' | 'left' | 'right' + +export type SymbolType = + | 'circle' + | 'rect' + | 'roundRect' + | 'triangle' + | 'diamond' + | 'pin' + | 'arrow' + | 'none' + +// 图表主题配置 +export interface ChartThemeConfig { + /** 图表高度 */ + chartHeight: string + /** 字体大小 */ + fontSize: number + /** 字体颜色 */ + fontColor: string + /** 主题颜色 */ + themeColor: string + /** 颜色组 */ + colors: string[] +} + +// 图表初始化选项 +export interface UseChartOptions { + /** 初始化选项 */ + initOptions?: EChartsOption + /** 延迟初始化时间(ms) */ + initDelay?: number + /** IntersectionObserver阈值 */ + threshold?: number + /** 是否自动响应主题变化 */ + autoTheme?: boolean +} + +// 基础图表 Props 接口 - 统一所有图表的基础属性 +export interface BaseChartProps { + /** 图表高度 */ + height?: string + /** 是否加载中 */ + loading?: boolean + isEmpty?: boolean + /** 颜色配置 */ + colors?: string[] +} + +// 轴线显示控制接口 - 统一轴线相关配置 +export interface AxisDisplayProps { + /** 是否显示坐标轴标签 */ + showAxisLabel?: boolean + /** 是否显示坐标轴线 */ + showAxisLine?: boolean + /** 是否显示分割线 */ + showSplitLine?: boolean +} + +// 交互显示控制接口 - 统一交互相关配置 +export interface InteractionProps { + /** 是否显示提示框 */ + showTooltip?: boolean + /** 是否显示图例 */ + showLegend?: boolean + /** 图例位置 */ + legendPosition?: LegendPosition +} + +// 柱状图数据项接口 +export interface BarDataItem { + /** 系列名称 */ + 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[] +} + +// 折线图数据项接口 +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 +} + +// 折线图 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 +} + +// 雷达图数据项接口 +export interface RadarDataItem { + /** 系列名称 */ + name: string + /** 数据值 */ + value: number[] +} + +// 雷达图 Props 接口 - 统一雷达图配置 +export interface RadarChartProps extends BaseChartProps, InteractionProps { + /** 雷达图指标配置 */ + indicator?: Array<{ name: string; max: number }> + /** 图表数据 */ + data?: RadarDataItem[] +} + +// 饼图/环形图数据项接口 +export interface PieDataItem { + /** 数据值 */ + value: number + /** 数据名称 */ + name: string +} + +// 环形图 Props 接口 - 统一环形图配置 +export interface RingChartProps extends BaseChartProps, InteractionProps { + /** 图表数据 */ + data: PieDataItem[] + /** 内外半径 */ + radius?: string[] + /** 边框圆角 */ + borderRadius?: number + /** 中心文本 */ + centerText?: string + /** 是否显示标签 */ + showLabel?: boolean +} + +// K线图数据项接口 +export interface KLineDataItem { + /** 时间标签 */ + 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 +} + +// 散点图数据项接口 +export interface ScatterDataItem { + /** 坐标值 [x, y] */ + value: number[] +} + +// 散点图 Props 接口 - 统一散点图配置 +export interface ScatterChartProps extends BaseChartProps, AxisDisplayProps, InteractionProps { + /** 图表数据 */ + data?: ScatterDataItem[] + /** 散点大小 */ + symbolSize?: number +} + +// 双柱对比图 Props 接口 - 统一双柱对比图配置 +export interface DualBarCompareChartProps extends BaseChartProps { + /** 上方数据 */ + 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 +} + +// 双向堆叠柱状图 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[] +} + +// 图表配置生成器函数类型 +export type ChartOptionGenerator = () => EChartsOption + +// 图表事件回调类型 +export type ChartEventCallback = (params: any) => void + +// 图表错误信息接口 +export interface ChartError { + /** 错误码 */ + code: string + /** 错误信息 */ + message: string + /** 错误详情 */ + details?: any +} diff --git a/adminSystem/src/types/component/index.ts b/adminSystem/src/types/component/index.ts new file mode 100644 index 0000000..cd89bce --- /dev/null +++ b/adminSystem/src/types/component/index.ts @@ -0,0 +1,145 @@ +/** + * 组件类型定义模块 + * + * 提供项目组件的类型定义 + * + * ## 主要功能 + * + * - 搜索组件类型定义 + * - 表格列配置类型 + * - 分页配置类型 + * - 表单规则类型 + * - 对话框配置类型 + * + * ## 使用场景 + * + * - 组件 Props 类型约束 + * - 组件配置类型定义 + * - 组件事件参数类型 + * + * @module types/component/index + * @author Art Design Pro Team + */ + +// 搜索组件类型 +export type SearchComponentType = + | 'input' + | 'select' + | 'radio' + | 'checkbox' + | 'date' + | 'datetime' + | 'daterange' + | 'datetimerange' + | 'month' + | 'monthrange' + | 'year' + | 'yearrange' + | 'week' + | 'time' + | 'timerange' + +// 搜索框值变化参数 +export interface SearchChangeParams { + 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 +} + +// 分页配置 +export interface PaginationConfig { + // 当前页 + 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 +} + +// 对话框配置 +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 +} diff --git a/adminSystem/src/types/config/index.ts b/adminSystem/src/types/config/index.ts new file mode 100644 index 0000000..dd144de --- /dev/null +++ b/adminSystem/src/types/config/index.ts @@ -0,0 +1,211 @@ +/** + * 配置类型定义模块 + * + * 提供系统配置相关的类型定义 + * + * ## 主要功能 + * + * - 主题设置类型 + * - 菜单布局类型 + * - 节日配置类型 + * - 系统基础配置类型 + * - 快速入口配置类型 + * - 顶部栏功能配置类型 + * - 环境配置类型 + * - 应用配置类型 + * + * ## 使用场景 + * + * - 系统配置文件类型约束 + * - 配置项类型定义 + * - 配置数据验证 + * + * @module types/config/index + * @author Art Design Pro Team + */ + +import { MenuTypeEnum, SystemThemeEnum } from '@/enums/appEnum' +import { MenuThemeType, SystemThemeTypes } from '@/types/store' + +// 主题设置 +export interface ThemeSetting { + /** 主题名称 */ + name: string + /** 系统主题类型 */ + theme: SystemThemeEnum + /** 主题颜色数组 */ + color: string[] + /** 左侧线条颜色 */ + leftLineColor: string + /** 右侧线条颜色 */ + rightLineColor: string + /** 主题图片 */ + img: string +} + +// 菜单布局 +export interface MenuLayout { + /** 布局名称 */ + 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 +} + +// 系统基础配置 +export interface SystemBasicConfig { + // 系统名称 + 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 +} + +// 快速入口应用项 +export interface FastEnterApplication extends FastEnterBaseItem { + /** 应用描述 */ + description: string + /** 图标代码 */ + icon: string + /** 图标颜色 */ + iconColor: string +} + +// 快速链接项 +export type FastEnterQuickLink = FastEnterBaseItem + +// 快速入口配置 +export interface FastEnterConfig { + /** 应用列表 */ + 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 +} + +// 环境配置 +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 +} + +// 应用配置 +export interface AppConfig extends SystemConfig { + // 环境配置 + env: EnvConfig + // 开发模式 + isDev: boolean + // 生产模式 + isProd: boolean + // 测试模式 + isTest: boolean +} + +// 功能配置项基础接口 +export interface FeatureConfigItem { + 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 +} diff --git a/adminSystem/src/types/index.ts b/adminSystem/src/types/index.ts new file mode 100644 index 0000000..9032fd2 --- /dev/null +++ b/adminSystem/src/types/index.ts @@ -0,0 +1,22 @@ +/** + * 类型定义统一导出模块 + * 提供全局类型定义的统一导出入口 + * + * @module types/index + * @author Art Design Pro Team + */ + +/** 通用类型定义(基础类型、工具类型等) */ +export * from './common' + +/** 组件相关类型定义 */ +export * from './component' + +/** 状态管理相关类型定义 */ +export * from './store' + +/** 路由相关类型定义 */ +export * from './router' + +/** 配置相关类型定义 */ +export * from './config' diff --git a/adminSystem/src/types/router/index.ts b/adminSystem/src/types/router/index.ts new file mode 100644 index 0000000..d9ef012 --- /dev/null +++ b/adminSystem/src/types/router/index.ts @@ -0,0 +1,80 @@ +/** + * 路由类型定义模块 + * + * 提供路由相关的类型定义 + * + * ## 主要功能 + * + * - 路由元数据类型(标题、图标、权限等) + * - 应用路由记录类型 + * - 路由配置扩展 + * + * ## 使用场景 + * + * - 路由配置类型约束 + * - 路由元数据定义 + * - 菜单生成 + * - 权限控制 + * + * @module types/router/index + * @author Art Design Pro Team + */ + +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 +} + +/** + * 应用路由记录接口 + * 扩展 Vue Router 的路由记录类型 + */ +export interface AppRouteRecord extends Omit { + id?: number + meta: RouteMeta + children?: AppRouteRecord[] + component?: string | (() => Promise) +} diff --git a/adminSystem/src/types/store/index.ts b/adminSystem/src/types/store/index.ts new file mode 100644 index 0000000..019801e --- /dev/null +++ b/adminSystem/src/types/store/index.ts @@ -0,0 +1,157 @@ +/** + * Store 状态类型定义模块 + * + * 提供 Pinia Store 的状态类型定义 + * + * ## 主要功能 + * + * - 系统主题类型 + * - 菜单主题类型 + * - 设置状态类型 + * - 工作标签页类型 + * - 用户状态类型 + * - 菜单状态类型 + * - 根状态类型 + * + * ## 使用场景 + * + * - Store 状态类型约束 + * - 状态数据结构定义 + * - 类型提示和自动补全 + * + * @module types/store/index + * @author Art Design Pro Team + */ + +import { MenuThemeEnum, SystemThemeEnum } from '@/enums/appEnum' +import { LocationQueryRaw } from 'vue-router' + +// 系统主题样式(light | dark) +export interface SystemThemeType { + /** 主题类名 */ + className: string +} + +// 定义包含多个主题的类型 +export type SystemThemeTypes = { + [key in Exclude]: SystemThemeType +} + +// 菜单主题样式 +export interface MenuThemeType { + /** 主题类型 */ + 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 +} + +// 多标签 +export interface WorkTab { + /** 标签标题 */ + 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[] +} + +// 设置Store状态 +export interface SettingStoreState extends SettingState { + // 额外的设置状态 + /** 菜单是否折叠 */ + collapsed: boolean + /** 设备类型 */ + device: 'desktop' | 'mobile' + /** 当前语言 */ + language: string +} + +// 工作标签页Store状态 +export interface WorkTabState { + /** 标签页列表 */ + tabs: WorkTab[] + /** 当前激活的标签页 */ + activeTab: string + /** 缓存的标签页列表 */ + cachedTabs: string[] +} + +// 菜单Store状态 +export interface MenuState { + /** 菜单列表 */ + menuList: any[] + /** 菜单是否已加载 */ + isLoaded: boolean + /** 菜单是否折叠 */ + collapsed: boolean +} + +// 根Store状态类型 +export interface RootState { + /** 用户状态 */ + user: UserState + /** 设置状态 */ + setting: SettingStoreState + /** 工作标签页状态 */ + workTab: WorkTabState + /** 菜单状态 */ + menu: MenuState +} diff --git a/adminSystem/src/utils/constants/index.ts b/adminSystem/src/utils/constants/index.ts new file mode 100644 index 0000000..831be29 --- /dev/null +++ b/adminSystem/src/utils/constants/index.ts @@ -0,0 +1,8 @@ +/** + * 常量定义相关工具函数统一导出 + * + * @module utils/constants/index + * @author Art Design Pro Team + */ + +export * from './links' diff --git a/adminSystem/src/utils/constants/links.ts b/adminSystem/src/utils/constants/links.ts new file mode 100644 index 0000000..06d297e --- /dev/null +++ b/adminSystem/src/utils/constants/links.ts @@ -0,0 +1,35 @@ +/** + * 网站链接常量配置 + * 集中管理便于维护和更新链接地址 + * + * @module utils/constants/links + * @author Art Design Pro Team + */ +export const WEB_LINKS = { + // Github 主页 + GITHUB_HOME: 'https://github.com/Daymychen/art-design-pro', + + // 项目 Github 主页 + GITHUB: 'https://github.com/Daymychen/art-design-pro', + + // 个人博客 + BLOG: 'https://www.artd.pro', + + // 项目文档 + DOCS: 'https://www.artd.pro/docs/zh/', + + // 精简版本 + LiteVersion: 'https://www.artd.pro/docs/zh/guide/lite-version.html', + + // v2.6.1版本 + OldVersion: 'https://www.artd.pro/v2/', + + // 项目社区 + COMMUNITY: 'https://www.artd.pro/docs/zh/community/communicate.html', + + // 个人 Bilibili 主页 + BILIBILI: 'https://space.bilibili.com/425500936?spm_id_from=333.1007.0.0', + + // 项目介绍 + INTRODUCE: 'https://www.artd.pro/docs/zh/guide/introduce.html' +} diff --git a/adminSystem/src/utils/form/index.ts b/adminSystem/src/utils/form/index.ts new file mode 100644 index 0000000..ed23a46 --- /dev/null +++ b/adminSystem/src/utils/form/index.ts @@ -0,0 +1,12 @@ +/** + * 表单工具函数统一导出 + * + * @module utils/form + * @author Art Design Pro Team + */ + +// 表单验证器 +export * from './validator' + +// 响应式布局 +export * from './responsive' diff --git a/adminSystem/src/utils/form/responsive.ts b/adminSystem/src/utils/form/responsive.ts new file mode 100644 index 0000000..c11df92 --- /dev/null +++ b/adminSystem/src/utils/form/responsive.ts @@ -0,0 +1,122 @@ +/** + * 表单响应式布局工具模块 + * + * 提供表单项在不同屏幕尺寸下的智能布局计算 + * + * ## 主要功能 + * + * - 响应式断点管理(xs/sm/md/lg/xl) + * - 表单列宽自动降级(避免小屏幕压缩) + * - 基于阈值的智能 span 计算 + * - 响应式计算器工厂函数 + * - 可配置的断点规则 + * + * ## 使用场景 + * + * - 表单组件响应式布局 + * - 搜索表单自适应 + * - 移动端表单优化 + * - 多列表单布局 + * + * ## 断点说明(基于 Element Plus Grid 24 栅格系统): + * - xs (手机): < 768px,小于 12 时降级为 24(满宽) + * - sm (平板): ≥ 768px,小于 12 时降级为 12(半宽) + * - md (中等屏幕): ≥ 992px,小于 8 时降级为 8(三分之一宽) + * - lg (大屏幕): ≥ 1200px,直接使用设置的 span + * - xl (超大屏幕): ≥ 1920px,直接使用设置的 span + * + * ## 核心功能 + * + * - calculateResponsiveSpan: 计算响应式列宽 + * - createResponsiveSpanCalculator: 创建 span 计算器(柯里化) + * + * @module utils/form/responsive + * @author Art Design Pro Team + */ + +/** + * 响应式断点类型 + */ +export type ResponsiveBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' + +/** + * 断点配置映射 + */ +interface BreakpointConfig { + /** 最小 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 +} + +/** + * 计算响应式列宽 + * + * 根据屏幕尺寸智能降级,避免小屏幕上表单项被压缩过小 + * + * @param itemSpan 表单项自定义的 span 值 + * @param defaultSpan 默认的 span 值 + * @param breakpoint 当前断点 + * @returns 计算后的 span 值 + * + * @example + * ```ts + * // 在 xs 断点下,span 为 6 会降级为 24(满宽) + * calculateResponsiveSpan(6, 6, 'xs') // 24 + * + * // 在 md 断点下,span 为 6 会降级为 8(三分之一宽) + * calculateResponsiveSpan(6, 6, 'md') // 8 + * + * // 在 lg 断点下,直接使用原始 span + * calculateResponsiveSpan(6, 6, 'lg') // 6 + * ``` + */ +export function calculateResponsiveSpan( + itemSpan: number | undefined, + defaultSpan: number, + breakpoint: ResponsiveBreakpoint +): number { + const finalSpan = itemSpan ?? defaultSpan + const config = BREAKPOINT_CONFIG[breakpoint] + + // 如果没有配置(lg/xl),直接返回原始 span + if (!config) { + return finalSpan + } + + // 如果 span 小于阈值,使用降级值 + return finalSpan >= config.threshold ? finalSpan : config.fallback +} + +/** + * 创建响应式 span 计算器 + * + * 返回一个函数,用于计算指定断点下的 span 值 + * + * @param defaultSpan 默认的 span 值 + * @returns span 计算函数 + * + * @example + * ```ts + * const getColSpan = createResponsiveSpanCalculator(6) + * getColSpan(undefined, 'xs') // 24 + * getColSpan(8, 'md') // 8 + * getColSpan(12, 'lg') // 12 + * ``` + */ +export function createResponsiveSpanCalculator(defaultSpan: number) { + return (itemSpan: number | undefined, breakpoint: ResponsiveBreakpoint): number => { + return calculateResponsiveSpan(itemSpan, defaultSpan, breakpoint) + } +} diff --git a/adminSystem/src/utils/form/validator.ts b/adminSystem/src/utils/form/validator.ts new file mode 100644 index 0000000..3670763 --- /dev/null +++ b/adminSystem/src/utils/form/validator.ts @@ -0,0 +1,316 @@ +/** + * 表单验证工具模块 + * + * 提供全面的表单字段验证功能 + * + * ## 主要功能 + * + * - 手机号码验证(中国大陆格式) + * - 固定电话验证(支持区号格式) + * - 用户账号验证(字母开头,支持数字和下划线) + * - 密码强度验证(普通密码、强密码) + * - 密码强度评估(弱、中、强) + * - IPv4 地址验证 + * - 邮箱地址验证(RFC 5322 标准) + * - URL 地址验证 + * - 身份证号码验证(18位,含校验码验证) + * - 银行卡号验证(Luhn 算法) + * - 字符串空格处理 + * + * ## 验证规则 + * + * - 手机号:1开头,第二位3-9,共11位 + * - 账号:字母开头,5-20位,支持字母数字下划线 + * - 普通密码:6-20位,必须包含字母和数字 + * - 强密码:8-20位,必须包含大小写字母、数字和特殊字符 + * - 身份证:18位,含出生日期和校验码验证 + * - 银行卡:13-19位,通过 Luhn 算法验证 + * + * @module utils/validation/formValidator + * @author Art Design Pro Team + */ + +/** + * 密码强度级别枚举 + */ +export enum PasswordStrength { + WEAK = '弱', + MEDIUM = '中', + STRONG = '强' +} + +/** + * 去除字符串首尾空格 + * @param value 待处理的字符串 + * @returns 返回去除首尾空格后的字符串 + */ +export function trimSpaces(value: string): string { + if (typeof value !== 'string') { + return '' + } + return value.trim() +} + +/** + * 验证手机号码(中国大陆) + * @param value 手机号码字符串 + * @returns 返回验证结果,true表示格式正确 + */ +export function validatePhone(value: string): boolean { + if (!value || typeof value !== 'string') { + return false + } + + // 中国大陆手机号码:1开头,第二位为3-9,共11位数字 + const phoneRegex = /^1[3-9]\d{9}$/ + return phoneRegex.test(value.trim()) +} + +/** + * 验证固定电话号码(中国大陆) + * @param value 电话号码字符串 + * @returns 返回验证结果,true表示格式正确 + */ +export function validateTelPhone(value: string): boolean { + 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, '')) +} + +/** + * 验证用户账号 + * @param value 账号字符串 + * @returns 返回验证结果,true表示格式正确 + * @description 规则:字母开头,5-20位,支持字母、数字、下划线 + */ +export function validateAccount(value: string): boolean { + 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()) +} + +/** + * 验证密码 + * @param value 密码字符串 + * @returns 返回验证结果,true表示格式正确 + * @description 规则:6-20位,必须包含字母和数字 + */ +export function validatePassword(value: string): boolean { + if (!value || typeof value !== 'string') { + return false + } + + const trimmedValue = value.trim() + + // 长度检查 + if (trimmedValue.length < 6 || trimmedValue.length > 20) { + return false + } + + // 必须包含字母和数字 + const hasLetter = /[a-zA-Z]/.test(trimmedValue) + const hasNumber = /\d/.test(trimmedValue) + + return hasLetter && hasNumber +} + +/** + * 验证强密码 + * @param value 密码字符串 + * @returns 返回验证结果,true表示格式正确 + * @description 规则:8-20位,必须包含大写字母、小写字母、数字和特殊字符 + */ +export function validateStrongPassword(value: string): boolean { + if (!value || typeof value !== 'string') { + return false + } + + const trimmedValue = value.trim() + + // 长度检查 + 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) + + return hasUpperCase && hasLowerCase && hasNumber && hasSpecialChar +} + +/** + * 获取密码强度 + * @param value 密码字符串 + * @returns 返回密码强度:弱、中、强 + * @description 弱:纯数字/纯字母/纯特殊字符;中:两种组合;强:三种或以上组合 + */ +export function getPasswordStrength(value: string): PasswordStrength { + if (!value || typeof value !== 'string') { + return PasswordStrength.WEAK + } + + const trimmedValue = value.trim() + + 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 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 + } +} + +/** + * 验证IPv4地址 + * @param value IP地址字符串 + * @returns 返回验证结果,true表示格式正确 + */ +export function validateIPv4Address(value: string): boolean { + 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})$/ + + if (!ipRegex.test(trimmedValue)) { + return false + } + + // 额外检查每个段是否在有效范围内 + const segments = trimmedValue.split('.') + return segments.every((segment) => { + const num = parseInt(segment, 10) + return num >= 0 && num <= 255 + }) +} + +/** + * 验证邮箱地址 + * @param value 邮箱地址字符串 + * @returns 返回验证结果,true表示格式正确 + */ +export function validateEmail(value: string): boolean { + if (!value || typeof value !== 'string') { + return false + } + + 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])?)*$/ + + return emailRegex.test(trimmedValue) && trimmedValue.length <= 254 +} + +/** + * 验证URL地址 + * @param value URL字符串 + * @returns 返回验证结果,true表示格式正确 + */ +export function validateURL(value: string): boolean { + if (!value || typeof value !== 'string') { + return false + } + + try { + new URL(value.trim()) + return true + } catch { + return false + } +} + +/** + * 验证身份证号码(中国大陆) + * @param value 身份证号码字符串 + * @returns 返回验证结果,true表示格式正确 + */ +export function validateChineseIDCard(value: string): boolean { + if (!value || typeof value !== 'string') { + return false + } + + 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]$/ + + 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'] + + 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 +} + +/** + * 验证银行卡号 + * @param value 银行卡号字符串 + * @returns 返回验证结果,true表示格式正确 + */ +export function validateBankCard(value: string): boolean { + if (!value || typeof value !== 'string') { + return false + } + + const trimmedValue = value.trim().replace(/\s+/g, '') + + // 银行卡号通常为13-19位数字 + if (!/^\d{13,19}$/.test(trimmedValue)) { + return false + } + + // Luhn算法验证 + let sum = 0 + let shouldDouble = false + + 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 + } + } + + sum += digit + shouldDouble = !shouldDouble + } + + return sum % 10 === 0 +} diff --git a/adminSystem/src/utils/http/error.ts b/adminSystem/src/utils/http/error.ts new file mode 100644 index 0000000..0f2c1ae --- /dev/null +++ b/adminSystem/src/utils/http/error.ts @@ -0,0 +1,182 @@ +/** + * HTTP 错误处理模块 + * + * 提供统一的 HTTP 请求错误处理机制 + * + * ## 主要功能 + * + * - 自定义 HttpError 错误类,封装错误信息、状态码、时间戳等 + * - 错误拦截和转换,将 Axios 错误转换为标准的 HttpError + * - 错误消息国际化处理,根据状态码返回对应的多语言错误提示 + * - 错误日志记录,便于问题追踪和调试 + * - 错误和成功消息的统一展示 + * - 类型守卫函数,用于判断错误类型 + * + * ## 使用场景 + * + * - HTTP 请求拦截器中统一处理错误 + * - 业务代码中捕获和处理特定错误 + * - 错误日志收集和上报 + * + * @module utils/http/error + * @author Art Design Pro Team + */ +import { AxiosError } from 'axios' +import { ApiStatus } from './status' +import { $t } from '@/locales' + +// 错误响应接口 +export interface ErrorResponse { + /** 错误状态码 */ + code: number + /** 错误消息 */ + msg: string + /** 错误附加数据 */ + data?: unknown +} + +// 错误日志数据接口 +export interface ErrorLogData { + /** 错误状态码 */ + 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 + + 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 + } + } +} + +/** + * 获取错误消息 + * @param status 错误状态码 + * @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' + } + + return $t(errorMap[status] || 'httpMsg.internalServerError') +} + +/** + * 处理错误 + * @param error 错误对象 + * @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) + } + + 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() + }) + } + + // 处理 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() + }) +} + +/** + * 显示错误消息 + * @param error 错误对象 + * @param showMessage 是否显示错误消息 + */ +export function showError(error: HttpError, showMessage: boolean = true): void { + if (showMessage) { + ElMessage.error(error.message) + } + // 记录错误日志 + console.error('[HTTP Error]', error.toLogData()) +} + +/** + * 显示成功消息 + * @param message 成功消息 + * @param showMessage 是否显示消息 + */ +export function showSuccess(message: string, showMessage: boolean = true): void { + if (showMessage) { + ElMessage.success(message) + } +} + +/** + * 判断是否为 HttpError 类型 + * @param error 错误对象 + * @returns 是否为 HttpError 类型 + */ +export const isHttpError = (error: unknown): error is HttpError => { + return error instanceof HttpError +} diff --git a/adminSystem/src/utils/http/index.ts b/adminSystem/src/utils/http/index.ts new file mode 100644 index 0000000..0e0002a --- /dev/null +++ b/adminSystem/src/utils/http/index.ts @@ -0,0 +1,214 @@ +/** + * HTTP 请求封装模块 + * 基于 Axios 封装的 HTTP 请求工具,提供统一的请求/响应处理 + * + * ## 主要功能 + * + * - 请求/响应拦截器(自动添加 Token、统一错误处理) + * - 401 未授权自动登出(带防抖机制) + * - 请求失败自动重试(可配置) + * - 统一的成功/错误消息提示 + * - 支持 GET/POST/PUT/DELETE 等常用方法 + * + * @module utils/http + * @author Art Design Pro Team + */ + +import axios, { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios' +import { useUserStore } from '@/store/modules/user' +import { ApiStatus } from './status' +import { HttpError, handleError, showError, showSuccess } from './error' +import { $t } from '@/locales' +import { BaseResponse } from '@/types' + +/** 请求配置常量 */ +const REQUEST_TIMEOUT = 15000 +const LOGOUT_DELAY = 500 +const MAX_RETRIES = 0 +const RETRY_DELAY = 1000 +const UNAUTHORIZED_DEBOUNCE_TIME = 3000 + +/** 401防抖状态 */ +let isUnauthorizedErrorShown = false +let unauthorizedTimer: NodeJS.Timeout | null = null + +/** 扩展 AxiosRequestConfig */ +interface ExtendedAxiosRequestConfig extends AxiosRequestConfig { + 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 + } + ] +}) + +/** 请求拦截器 */ +axiosInstance.interceptors.request.use( + (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) + } + + 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)) + } +) + +/** 统一创建HttpError */ +function createHttpError(message: string, code: number) { + return new HttpError(message, code) +} + +/** 处理401错误(带防抖) */ +function handleUnauthorizedError(message?: string): never { + const error = createHttpError(message || $t('httpMsg.unauthorized'), ApiStatus.unauthorized) + + if (!isUnauthorizedErrorShown) { + isUnauthorizedErrorShown = true + logOut() + + unauthorizedTimer = setTimeout(resetUnauthorizedError, UNAUTHORIZED_DEBOUNCE_TIME) + + showError(error, true) + throw error + } + + throw error +} + +/** 重置401防抖状态 */ +function resetUnauthorizedError() { + isUnauthorizedErrorShown = false + if (unauthorizedTimer) clearTimeout(unauthorizedTimer) + unauthorizedTimer = null +} + +/** 退出登录函数 */ +function logOut() { + setTimeout(() => { + useUserStore().logOut() + }, LOGOUT_DELAY) +} + +/** 是否需要重试 */ +function shouldRetry(statusCode: number) { + return [ + ApiStatus.requestTimeout, + ApiStatus.internalServerError, + ApiStatus.badGateway, + ApiStatus.serviceUnavailable, + ApiStatus.gatewayTimeout + ].includes(statusCode) +} + +/** 请求重试逻辑 */ +async function retryRequest( + 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 + } +} + +/** 延迟函数 */ +function delay(ms: number) { + 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 + } + + try { + const res = await axiosInstance.request>(config) + + // 显示成功消息 + 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) + } +} + +/** 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) + } +} + +export default api diff --git a/adminSystem/src/utils/http/status.ts b/adminSystem/src/utils/http/status.ts new file mode 100644 index 0000000..989bb37 --- /dev/null +++ b/adminSystem/src/utils/http/status.ts @@ -0,0 +1,18 @@ +/** + * 接口状态码 + */ +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版本不支持 +} diff --git a/adminSystem/src/utils/index.ts b/adminSystem/src/utils/index.ts new file mode 100644 index 0000000..f1e1b77 --- /dev/null +++ b/adminSystem/src/utils/index.ts @@ -0,0 +1,34 @@ +/** + * Utils 工具函数统一导出 + * 提供向后兼容性和便捷导入 + * + * @module utils/index + * @author Art Design Pro Team + */ + +// UI 相关 +export * from './ui' + +// 路由相关 +export * from './router' + +// 路由导航相关 +export * from './navigation' + +// 系统管理相关 +export * from './sys' + +// 常量定义相关 +export * from './constants' + +// 存储相关 +export * from './storage' + +// HTTP 相关 +export * from './http' + +// 表单相关 +export * from './form' + +// socket 相关 +export * from './socket' diff --git a/adminSystem/src/utils/navigation/index.ts b/adminSystem/src/utils/navigation/index.ts new file mode 100644 index 0000000..0b84e78 --- /dev/null +++ b/adminSystem/src/utils/navigation/index.ts @@ -0,0 +1,10 @@ +/** + * 路由和导航相关工具函数统一导出 + * + * @module utils/navigation/index + * @author Art Design Pro Team + */ + +export * from './jump' +export * from './worktab' +export * from './route' diff --git a/adminSystem/src/utils/navigation/jump.ts b/adminSystem/src/utils/navigation/jump.ts new file mode 100644 index 0000000..4fde1e8 --- /dev/null +++ b/adminSystem/src/utils/navigation/jump.ts @@ -0,0 +1,62 @@ +/** + * 导航跳转工具模块 + * + * 提供统一的页面跳转和导航功能 + * + * ## 主要功能 + * + * - 外部链接打开(新窗口) + * - 菜单项跳转处理(支持内部路由和外部链接) + * - iframe 页面跳转支持 + * - 递归查找并跳转到第一个可见的子菜单 + * - 智能判断跳转目标类型(外部链接/内部路由) + * + * @module utils/navigation/jump + * @author Art Design Pro Team + */ +import { AppRouteRecord } from '@/types/router' +import { router } from '@/router' + +// 打开外部链接 +export const openExternalLink = (link: string) => { + window.open(link, '_blank') +} + +/** + * 菜单跳转 + * @param item 菜单项 + * @param jumpToFirst 是否跳转到第一个子菜单 + * @returns + */ +export const handleMenuJump = (item: AppRouteRecord, jumpToFirst: boolean = false) => { + // 处理外部链接 + const { link, isIframe } = item.meta + if (link && !isIframe) { + return openExternalLink(link) + } + + // 如果不需要跳转到第一个子菜单,或者没有子菜单,直接跳转当前路径 + 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 firstChild = findFirstLeafMenu(item.children) + + // 如果第一个子菜单是外部链接则打开新窗口 + if (firstChild.meta?.link) { + return openExternalLink(firstChild.meta.link) + } + + // 跳转到子菜单路径 + router.push(firstChild.path) +} diff --git a/adminSystem/src/utils/navigation/route.ts b/adminSystem/src/utils/navigation/route.ts new file mode 100644 index 0000000..9ca4f29 --- /dev/null +++ b/adminSystem/src/utils/navigation/route.ts @@ -0,0 +1,78 @@ +/** + * 路由工具模块 + * + * 提供路由处理和菜单路径相关的工具函数 + * + * ## 主要功能 + * + * - iframe 路由检测,判断是否为外部嵌入页面 + * - 菜单项有效性验证,过滤隐藏和无效菜单 + * - 路径标准化处理,统一路径格式 + * - 递归查找菜单树中第一个有效路径 + * - 支持多级嵌套菜单的路径解析 + * + * ## 使用场景 + * + * - 系统初始化时获取默认跳转路径 + * - 菜单权限过滤后获取首个可访问页面 + * - 路由重定向逻辑处理 + * - iframe 页面特殊处理 + * + * @module utils/navigation/route + * @author Art Design Pro Team + */ + +import { AppRouteRecord } from '@/types' + +// 检查是否为 iframe 路由 +export function isIframe(url: string): boolean { + return url.startsWith('/outside/iframe/') +} + +/** + * 验证菜单项是否有效 + * @param menuItem 菜单项 + * @returns 是否为有效菜单项 + */ +const isValidMenuItem = (menuItem: AppRouteRecord): boolean => { + return !!(menuItem.path && menuItem.path.trim() && !menuItem.meta?.isHide) +} + +/** + * 标准化路径格式 + * @param path 路径 + * @returns 标准化后的路径 + */ +const normalizePath = (path: string): string => { + return path.startsWith('/') ? path : `/${path}` +} + +/** + * 递归获取菜单的第一个有效路径 + * @param menuList 菜单列表 + * @returns 第一个有效路径,如果没有找到则返回空字符串 + */ +export const getFirstMenuPath = (menuList: AppRouteRecord[]): string => { + if (!Array.isArray(menuList) || menuList.length === 0) { + return '' + } + + for (const menuItem of menuList) { + if (!isValidMenuItem(menuItem)) { + continue + } + + // 如果有子菜单,优先查找子菜单 + if (menuItem.children?.length) { + const childPath = getFirstMenuPath(menuItem.children) + if (childPath) { + return childPath + } + } + + // 返回当前菜单项的标准化路径 + return normalizePath(menuItem.path!) + } + + return '' +} diff --git a/adminSystem/src/utils/navigation/worktab.ts b/adminSystem/src/utils/navigation/worktab.ts new file mode 100644 index 0000000..6db6a77 --- /dev/null +++ b/adminSystem/src/utils/navigation/worktab.ts @@ -0,0 +1,67 @@ +/** + * 工作标签页管理模块 + * + * 提供工作标签页(Worktab)的自动管理功能 + * + * ## 主要功能 + * + * - 根据路由导航自动创建和更新工作标签页 + * - iframe 页面标签页特殊处理 + * - 标签页信息提取(标题、路径、缓存状态等) + * - 固定标签页支持 + * - 根据系统设置控制标签页显示 + * - 首页标签页特殊处理 + * + * ## 使用场景 + * + * - 路由守卫中自动创建标签页 + * - 页面切换时更新标签页状态 + * - 多标签页导航系统 + * + * @module utils/navigation/worktab + * @author Art Design Pro Team + */ +import { useWorktabStore } from '@/store/modules/worktab' +import { RouteLocationNormalized } from 'vue-router' +import { isIframe } from './route' +import { useSettingStore } from '@/store/modules/setting' +import { IframeRouteManager } from '@/router/core' +import { useCommon } from '@/hooks/core/useCommon' + +/** + * 根据当前路由信息设置工作标签页(worktab) + * @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) + + 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/adminSystem/src/utils/router.ts b/adminSystem/src/utils/router.ts new file mode 100644 index 0000000..8c838ff --- /dev/null +++ b/adminSystem/src/utils/router.ts @@ -0,0 +1,61 @@ +/** + * 路由工具函数 + * + * 提供路由相关的工具函数 + * + * @module utils/router + */ +import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router' +import AppConfig from '@/config' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import i18n, { $t } from '@/locales' + +/** 扩展的路由配置类型 */ +export type AppRouteRecordRaw = RouteRecordRaw & { + hidden?: boolean +} + +/** 顶部进度条配置 */ +export const configureNProgress = () => { + NProgress.configure({ + easing: 'ease', + speed: 600, + showSpinner: false, + parent: 'body' + }) +} + +/** + * 设置页面标题,根据路由元信息和系统信息拼接标题 + * @param to 当前路由对象 + */ +export const setPageTitle = (to: RouteLocationNormalized): void => { + const { title } = to.meta + if (title) { + setTimeout(() => { + document.title = `${formatMenuTitle(String(title))} - ${AppConfig.systemInfo.name}` + }, 150) + } +} + +/** + * 格式化菜单标题 + * @param title 菜单标题,可以是 i18n 的 key,也可以是字符串 + * @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 '' +} diff --git a/adminSystem/src/utils/socket/index.ts b/adminSystem/src/utils/socket/index.ts new file mode 100644 index 0000000..77d46ad --- /dev/null +++ b/adminSystem/src/utils/socket/index.ts @@ -0,0 +1,388 @@ +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) +} + +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 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 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秒 + } + + // 单例模式获取实例 + 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 + } + + // 如果已连接,不重复连接 + 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) + + // 设置连接超时检测 + 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() + } + } + + // 处理连接超时 + 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 + + if (this.ws) { + // 1000 表示正常关闭 + this.ws.close(force ? 1001 : 1000, force ? 'Force closed' : 'Normal close') + this.ws = null + } + + 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 + } + + // 如果未连接且不要求立即发送,则加入消息队列 + 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() + } + } + + // 发送队列中的消息 + 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 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}` + ) + + // 1000 是正常关闭代码 + const isNormalClose = event.code === 1000 + + this.isConnected = false + this.isConnecting = false + this.clearAllTimers() + + 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) : '未初始化' + ) + + this.isConnected = false + this.isConnecting = false + + // 只有在未停止重连的情况下才尝试重连 + 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 startHeartbeat(): void { + this.clearTimer('detectionTimer') + this.clearTimer('timeoutTimer') + + this.detectionTimer = setTimeout(() => { + this.isConnected = this.ws?.readyState === WebSocket.OPEN + + if (!this.isConnected) { + console.warn('WebSocket心跳检测失败,尝试重连') + this.reconnect() + + this.timeoutTimer = setTimeout(() => { + console.warn('WebSocket重连超时') + this.close() + }, this.reconnectTimeout) + } + }, this.heartbeatInterval) + } + + // 重置心跳检测 + private resetHeartbeat(): void { + this.clearTimer('detectionTimer') + this.clearTimer('timeoutTimer') + this.startHeartbeat() + } + + // 开始发送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 + } + + 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 + } + + // 检查是否超过最大重连次数 + if (this.reconnectAttempts >= this.maxReconnectAttempts) { + console.error(`已达到最大重连次数(${this.maxReconnectAttempts}),停止重连`) + this.close(true) + return + } + + this.reconnectAttempts++ + this.stopReconnect = true + this.close(true) + + 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) + } + + // 计算重连延迟 - 指数退避策略 + 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 clearAllTimers(): void { + this.clearTimer('detectionTimer') + this.clearTimer('timeoutTimer') + this.clearTimer('reconnectTimer') + this.clearTimer('pingTimer') + this.clearTimer('connectionTimer') + } + + // 获取当前连接状态 + 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 '已断开' + } + + // 销毁实例 + static destroyInstance(): void { + if (WebSocketClient.instance) { + WebSocketClient.instance.close() + WebSocketClient.instance = null + } + } +} diff --git a/adminSystem/src/utils/storage/index.ts b/adminSystem/src/utils/storage/index.ts new file mode 100644 index 0000000..a4366f0 --- /dev/null +++ b/adminSystem/src/utils/storage/index.ts @@ -0,0 +1,7 @@ +/** + * 存储相关工具函数统一导出 + */ + +export * from './storage' +export * from './storage-config' +export * from './storage-key-manager' diff --git a/adminSystem/src/utils/storage/storage-config.ts b/adminSystem/src/utils/storage/storage-config.ts new file mode 100644 index 0000000..c41b60b --- /dev/null +++ b/adminSystem/src/utils/storage/storage-config.ts @@ -0,0 +1,122 @@ +/** + * 存储配置管理模块 + * + * 提供统一的本地存储配置和工具方法 + * + * ## 主要功能 + * + * - 版本化存储键管理,支持多版本数据隔离 + * - 存储键名生成和解析(带版本前缀) + * - 版本号提取和验证 + * - 存储键匹配的正则表达式生成 + * - 旧版本存储键兼容处理 + * - 升级和登出延迟配置 + * - 主题存储键配置 + * + * ## 使用场景 + * + * - Pinia Store 持久化存储 + * - 应用版本升级时的数据迁移 + * - 多版本数据清理 + * - 存储键的统一管理和规范 + * + * 存储键格式:sys-v{version}-{storeId} + * 例如:sys-v1.0.0-user, sys-v1.0.0-setting + * + * @module utils/storage/storage-config + * @author Art Design Pro Team + */ +export class StorageConfig { + /** 当前应用版本 */ + static readonly CURRENT_VERSION = __APP_VERSION__ + + /** 存储键前缀 */ + static readonly STORAGE_PREFIX = 'sys-v' + + /** 版本键名 */ + static readonly VERSION_KEY = 'sys-version' + + /** 主题键名(index.html中使用了,如果修改,需要同步修改) */ + static readonly THEME_KEY = 'sys-theme' + + /** 上次登录用户ID键名(用于判断是否为同一用户登录) */ + static readonly LAST_USER_ID_KEY = 'sys-last-user-id' + + /** 跳过升级检查的版本 */ + static readonly SKIP_UPGRADE_VERSION = '1.0.0' + + /** 升级处理延迟时间(毫秒) */ + static readonly UPGRADE_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 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}$`) + } + + /** + * 创建当前版本存储键匹配的正则表达式 + */ + static createCurrentVersionPattern(): RegExp { + return new RegExp(`^${this.STORAGE_PREFIX}${this.CURRENT_VERSION}-`) + } + + /** + * 创建任意版本存储键匹配的正则表达式 + */ + static createVersionPattern(): RegExp { + return new RegExp(`^${this.STORAGE_PREFIX}`) + } + + /** + * 检查是否为当前版本的键 + */ + 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 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 + } +} diff --git a/adminSystem/src/utils/storage/storage-key-manager.ts b/adminSystem/src/utils/storage/storage-key-manager.ts new file mode 100644 index 0000000..ba14f65 --- /dev/null +++ b/adminSystem/src/utils/storage/storage-key-manager.ts @@ -0,0 +1,97 @@ +/** + * 存储键名管理器模块 + * + * 提供智能的版本化存储键管理和数据迁移功能 + * + * ## 主要功能 + * + * - 自动生成当前版本的存储键名 + * - 检测当前版本数据是否存在 + * - 查找其他版本的同名存储数据 + * - 自动将旧版本数据迁移到当前版本 + * - 数据迁移日志记录 + * - 迁移失败的错误处理 + * + * ## 使用场景 + * + * - Pinia Store 持久化插件中获取存储键 + * - 应用版本升级时自动迁移用户数据 + * - 避免版本升级导致的数据丢失 + * - 实现平滑的版本过渡 + * + * ## 工作流程 + * + * 1. 优先使用当前版本的存储键 + * 2. 如果当前版本无数据,查找其他版本的同名数据 + * 3. 找到旧版本数据后自动迁移到当前版本 + * 4. 返回当前版本的存储键供使用 + * + * @module utils/storage/storage-key-manager + * @author Art Design Pro Team + */ +import { StorageConfig } from '@/utils/storage' + +/** + * 存储键名管理器 + * 负责处理版本化的存储键名生成和数据迁移 + */ +export class StorageKeyManager { + /** + * 获取当前版本的存储键名 + */ + private getCurrentVersionKey(storeId: string): string { + return StorageConfig.generateStorageKey(storeId) + } + + /** + * 检查当前版本的数据是否存在 + */ + 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) + + 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) + } + } + + /** + * 获取持久化存储的键名(支持自动数据迁移) + */ + getStorageKey(storeId: string): string { + const currentKey = this.getCurrentVersionKey(storeId) + + // 优先使用当前版本的数据 + if (this.hasCurrentVersionData(currentKey)) { + return currentKey + } + + // 查找并迁移其他版本的数据 + const existingKey = this.findExistingKey(storeId) + if (existingKey) { + this.migrateData(existingKey, currentKey) + } + + return currentKey + } +} diff --git a/adminSystem/src/utils/storage/storage.ts b/adminSystem/src/utils/storage/storage.ts new file mode 100644 index 0000000..67b9e9e --- /dev/null +++ b/adminSystem/src/utils/storage/storage.ts @@ -0,0 +1,250 @@ +/** + * 存储兼容性管理模块 + * + * 提供完整的本地存储兼容性检查和数据验证功能 + * + * 主要功能 + * + * - 多版本存储数据检测和验证 + * - 新旧存储格式兼容处理 + * - 存储数据完整性校验 + * - 存储异常自动恢复(清理+登出) + * - 登录状态验证 + * - 存储为空检测 + * - 版本号管理 + * + * ## 使用场景 + * + * - 应用启动时检查存储数据有效性 + * - 路由守卫中验证登录状态 + * - 版本升级时的数据兼容性检查 + * - 存储异常时的自动恢复 + * - 防止因存储数据损坏导致的系统异常 + * + * ## 工作流程 + * + * 1. 优先检查当前版本的存储数据 + * 2. 检查其他版本的存储数据 + * 3. 兼容旧格式的存储数据 + * 4. 验证数据完整性 + * 5. 异常时提示用户并执行登出 + * + * @module utils/storage/storage + * @author Art Design Pro Team + */ +import { router } from '@/router' +import { useUserStore } from '@/store/modules/user' +import { StorageConfig } from '@/utils/storage/storage-config' + +/** + * 存储兼容性管理器 + * 负责处理不同版本间的存储兼容性检查和数据验证 + */ +class StorageCompatibilityManager { + /** + * 获取系统版本号 + */ + 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 + } + + /** + * 检查当前版本是否有存储数据 + */ + private hasCurrentVersionStorage(): boolean { + const storageKeys = Object.keys(localStorage) + const currentVersionPattern = StorageConfig.createCurrentVersionPattern() + + return storageKeys.some( + (key) => currentVersionPattern.test(key) && localStorage.getItem(key) !== null + ) + } + + /** + * 检查是否存在任何版本的存储数据 + */ + private hasAnyVersionStorage(): boolean { + const storageKeys = Object.keys(localStorage) + const versionPattern = StorageConfig.createVersionPattern() + + 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 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 handleStorageError(): void { + this.showStorageError() + this.performSystemLogout() + } + + /** + * 验证存储数据完整性 + * @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 + } + + // 检查旧版本存储结构 + 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 + } + } + + /** + * 检查存储是否为空 + */ + isStorageEmpty(): boolean { + // 检查新版本存储结构 + if (this.hasCurrentVersionStorage()) { + return false + } + + // 检查是否有任何版本的存储数据 + if (this.hasAnyVersionStorage()) { + return false + } + + // 检查旧版本存储结构 + 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() + + if (isValid || isEmpty) { + // console.debug('[Storage] 存储兼容性检查通过') + return true + } + + console.warn('[Storage] 存储兼容性检查失败') + return false + } catch (error) { + console.error('[Storage] 兼容性检查异常:', error) + return false + } + } +} + +// 创建存储兼容性管理器实例 +const storageManager = new StorageCompatibilityManager() + +/** + * 获取系统存储数据 + */ +export function getSystemStorage(): any { + return storageManager.getSystemStorage() +} + +/** + * 获取系统版本号 + */ +export function getSysVersion(): string | null { + return storageManager.getSystemVersion() +} + +/** + * 验证本地存储数据 + * @param requireAuth 是否需要验证登录状态(默认 false) + */ +export function validateStorageData(requireAuth: boolean = false): boolean { + return storageManager.validateStorageData(requireAuth) +} + +/** + * 检查存储兼容性 + * @param requireAuth 是否需要验证登录状态(默认 false) + */ +export function checkStorageCompatibility(requireAuth: boolean = false): boolean { + return storageManager.checkCompatibility(requireAuth) +} diff --git a/adminSystem/src/utils/sys/console.ts b/adminSystem/src/utils/sys/console.ts new file mode 100644 index 0000000..1f89058 --- /dev/null +++ b/adminSystem/src/utils/sys/console.ts @@ -0,0 +1,13 @@ +// ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=display&f=Big&t=ABB%0A +const asciiArt = ` +\x1b[32m欢迎使用 Art Design Pro! +\x1b[0m +\x1b[36m哇!你居然在用我的项目~ 好用的话别忘了去 GitHub 点个 ★Star 呀,你的支持就是我更新的超强动力!祝使用体验满分💯 +\x1b[0m +\x1b[33mGitHub: https://github.com/Daymychen/art-design-pro +\x1b[0m +\x1b[31m技术支持(QQ群): 821834289,和开发者一起交流~ 群里有小伙伴实时答疑,遇到问题不用慌! +\x1b[0m +` + +console.log(asciiArt) diff --git a/adminSystem/src/utils/sys/error-handle.ts b/adminSystem/src/utils/sys/error-handle.ts new file mode 100644 index 0000000..22109c2 --- /dev/null +++ b/adminSystem/src/utils/sys/error-handle.ts @@ -0,0 +1,102 @@ +/** + * 全局错误处理模块 + * + * 提供统一的错误捕获和处理机制 + * + * ## 主要功能 + * + * - Vue 运行时错误捕获(组件错误、生命周期错误等) + * - 全局脚本错误捕获(语法错误、运行时错误等) + * - Promise 未捕获错误处理(unhandledrejection) + * - 静态资源加载错误监控(图片、脚本、样式等) + * - 错误日志记录和上报 + * - 统一的错误处理入口 + * + * ## 使用场景 + * - 应用启动时安装全局错误处理器 + * - 捕获和记录所有类型的错误 + * - 错误上报到监控平台 + * - 提升应用稳定性和可维护性 + * - 问题排查和调试 + * + * ## 错误类型 + * + * - VueError: Vue 组件相关错误 + * - ScriptError: JavaScript 脚本错误 + * - PromiseError: Promise 未捕获的 rejection + * - ResourceError: 静态资源加载失败 + * + * @module utils/sys/error-handle + * @author Art Design Pro Team + */ +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 }) +} + +/** + * 全局脚本错误处理 + */ +export function scriptErrorHandler( + 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 // 阻止默认控制台报错,可根据需求改 +} + +/** + * Promise 未捕获错误处理 + */ +export function registerPromiseErrorHandler() { + 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 // 捕获阶段才能监听到资源错误 + ) +} + +/** + * 安装统一错误处理 + */ +export function setupErrorHandle(app: App) { + app.config.errorHandler = vueErrorHandler + window.onerror = scriptErrorHandler + registerPromiseErrorHandler() + registerResourceErrorHandler() +} diff --git a/adminSystem/src/utils/sys/index.ts b/adminSystem/src/utils/sys/index.ts new file mode 100644 index 0000000..a2e0729 --- /dev/null +++ b/adminSystem/src/utils/sys/index.ts @@ -0,0 +1,6 @@ +/** + * 系统管理相关工具函数统一导出 + */ + +export * from './upgrade' +export { default as mittBus } from './mittBus' diff --git a/adminSystem/src/utils/sys/mittBus.ts b/adminSystem/src/utils/sys/mittBus.ts new file mode 100644 index 0000000..22f0108 --- /dev/null +++ b/adminSystem/src/utils/sys/mittBus.ts @@ -0,0 +1,63 @@ +/** + * 全局事件总线模块 + * + * 基于 mitt 库实现的类型安全的事件总线 + * + * ## 主要功能 + * + * - 跨组件通信(发布/订阅模式) + * - 类型安全的事件定义和调用 + * - 全局事件管理(烟花效果、设置面板、搜索对话框等) + * - 解耦组件间的直接依赖 + * + * ## 使用场景 + * + * - 跨层级组件通信 + * - 全局功能触发(设置、搜索、聊天、锁屏等) + * - 特效触发(烟花效果) + * - 避免 props 层层传递 + * + * ## 用法示例 + * + * ```typescript + * // 订阅事件 + * mittBus.on('openSetting', () => { ... }) + * + * // 发布事件 + * mittBus.emit('openSetting') + * + * // 带参数的事件 + * mittBus.emit('triggerFireworks', 'image-url') + * ``` + * + * ## 已定义的事件 + * + * - triggerFireworks: 触发烟花效果(可选图片URL) + * - openSetting: 打开设置面板 + * - openSearchDialog: 打开搜索对话框 + * - openChat: 打开聊天窗口 + * - openLockScreen: 打开锁屏 + * + * @module utils/sys/mittBus + * @author Art Design Pro Team + */ +import mitt, { type Emitter } from 'mitt' + +// 定义事件类型映射 +type Events = { + // 烟花效果事件 - 可选的图片URL参数 + triggerFireworks: string | undefined + // 打开设置面板事件 - 无参数 + openSetting: void + // 打开搜索对话框事件 - 无参数 + openSearchDialog: void + // 打开聊天窗口事件 - 无参数 + openChat: void + // 打开锁屏事件 - 无参数 + openLockScreen: void +} + +// 创建类型安全的事件总线实例 +const mittBus: Emitter = mitt() + +export default mittBus diff --git a/adminSystem/src/utils/sys/upgrade.ts b/adminSystem/src/utils/sys/upgrade.ts new file mode 100644 index 0000000..53d3465 --- /dev/null +++ b/adminSystem/src/utils/sys/upgrade.ts @@ -0,0 +1,277 @@ +/** + * 系统版本升级管理模块 + * + * 提供完整的应用版本升级检测和处理功能 + * + * ## 主要功能 + * + * - 版本号比较和升级检测 + * - 首次访问识别和处理 + * - 旧版本数据自动清理 + * - 升级日志展示和通知 + * - 强制重新登录控制(根据升级日志配置) + * - 版本号规范化处理 + * - 旧存储结构迁移和清理 + * - 升级流程延迟执行(确保应用完全加载) + * + * ## 使用场景 + * + * - 应用启动时自动检测版本升级 + * - 版本更新后清理旧数据 + * - 向用户展示版本更新内容 + * - 重大更新时要求用户重新登录 + * - 防止旧版本数据污染新版本 + * + * ## 工作流程 + * + * 1. 检查本地存储的版本号 + * 2. 与当前应用版本对比 + * 3. 查找并清理旧版本数据 + * 4. 展示升级通知(包含更新日志) + * 5. 根据配置决定是否强制重新登录 + * 6. 更新本地版本号 + * + * @module utils/sys/upgrade + * @author Art Design Pro Team + */ +import { upgradeLogList } from '@/mock/upgrade/changeLog' +import { ElNotification } from 'element-plus' +import { useUserStore } from '@/store/modules/user' +import { StorageConfig } from '@/utils/storage/storage-config' + +/** + * 版本管理器 + * 负责处理版本比较、升级检测和数据清理 + */ +class VersionManager { + /** + * 规范化版本号字符串,移除前缀 'v' + */ + private normalizeVersion(version: string): string { + return version.replace(/^v/, '') + } + + /** + * 获取存储的版本号 + */ + private getStoredVersion(): string | null { + return localStorage.getItem(StorageConfig.VERSION_KEY) + } + + /** + * 设置版本号到存储 + */ + private setStoredVersion(version: string): void { + localStorage.setItem(StorageConfig.VERSION_KEY, version) + } + + /** + * 检查是否应该跳过升级处理 + */ + private shouldSkipUpgrade(): boolean { + return StorageConfig.CURRENT_VERSION === StorageConfig.SKIP_UPGRADE_VERSION + } + + /** + * 检查是否为首次访问 + */ + private isFirstVisit(storedVersion: string | null): boolean { + return !storedVersion + } + + /** + * 检查版本是否相同 + */ + 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) // 移除末尾的 '-' + + // 查找旧的单一存储结构 + 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('-') + ) + + return { oldSysKey, oldVersionKeys } + } + + /** + * 检查是否需要重新登录 + */ + 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 + ) + }) + } + + /** + * 构建升级通知消息 + */ + private buildUpgradeMessage(requireReLogin: boolean): string { + const { title: content } = upgradeLogList.value[0] + + const messageParts = [ + `

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

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

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

` + ) + } + + return messageParts.join('') + } + + /** + * 显示升级通知 + */ + 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}`) + } + + // 清理旧版本的分离存储 + 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 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) + + // 显示升级通知 + this.showUpgradeNotification(message) + + // 更新版本号 + this.setStoredVersion(StorageConfig.CURRENT_VERSION) + + // 清理旧数据 + this.cleanupLegacyData(legacyStorage.oldSysKey, legacyStorage.oldVersionKeys) + + // 执行登出(如果需要) + if (requireReLogin) { + this.performLogout() + } + + console.info(`[Upgrade] 升级完成: ${storedVersion} → ${StorageConfig.CURRENT_VERSION}`) + } catch (error) { + console.error('[Upgrade] 系统升级处理失败:', error) + } + } + + /** + * 系统升级处理主流程 + */ + async processUpgrade(): Promise { + // 跳过特定版本 + if (this.shouldSkipUpgrade()) { + console.debug('[Upgrade] 跳过版本升级检查') + return + } + + const storedVersion = this.getStoredVersion() + + // 首次访问处理 + if (this.isFirstVisit(storedVersion)) { + this.setStoredVersion(StorageConfig.CURRENT_VERSION) + // console.info('[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 + } + + // 延迟执行升级流程,确保应用已完全加载 + setTimeout(() => { + this.executeUpgrade(storedVersion!, legacyStorage) + }, StorageConfig.UPGRADE_DELAY) + } +} + +// 创建版本管理器实例 +const versionManager = new VersionManager() + +/** + * 系统升级处理入口函数 + */ +export async function systemUpgrade(): Promise { + await versionManager.processUpgrade() +} diff --git a/adminSystem/src/utils/table/tableCache.ts b/adminSystem/src/utils/table/tableCache.ts new file mode 100644 index 0000000..045a7ce --- /dev/null +++ b/adminSystem/src/utils/table/tableCache.ts @@ -0,0 +1,266 @@ +/** + * 表格缓存管理模块 + * + * 提供高性能的表格数据缓存机制 + * + * ## 主要功能 + * + * - 基于参数的智能缓存键生成(使用 ohash) + * - LRU(最近最少使用)缓存淘汰策略 + * - 缓存过期时间管理 + * - 缓存大小限制和自动清理 + * - 基于标签的缓存分组管理 + * - 多种缓存失效策略(清空所有、清空当前、清空分页等) + * - 缓存访问统计和命中率分析 + * - 缓存大小估算 + * + * ## 使用场景 + * + * - 表格数据的分页缓存 + * - 减少重复的 API 请求 + * - 提升表格切换和返回的响应速度 + * - 搜索条件变化时的智能缓存管理 + * - 数据更新后的缓存失效处理 + * + * ## 缓存策略 + * + * - CLEAR_ALL: 清空所有缓存(适用于全局数据更新) + * - CLEAR_CURRENT: 仅清空当前查询条件的缓存(适用于单条数据更新) + * - CLEAR_PAGINATION: 清空所有分页缓存但保留不同搜索条件(适用于批量操作) + * - KEEP_ALL: 不清除缓存(适用于只读操作) + * + * @module utils/table/tableCache + * @author Art Design Pro Team + */ +import { hash } from 'ohash' + +// 缓存失效策略枚举 +export enum CacheInvalidationStrategy { + /** 清空所有缓存 */ + 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 +} + +// 缓存存储接口 +export interface CacheItem { + 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 + + 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 generateKey(params: unknown): string { + return hash(params) + } + + // 🔧 优化:增强类型安全性 + 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 + ) + + 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') + + return tags + } + + // 🔧 优化:LRU 缓存清理 + private evictLRU(): void { + if (this.cache.size <= this.maxSize) return + + // 找到最少使用的缓存项 + 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 + } + } + + 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() + + // 检查是否需要清理 + this.evictLRU() + + 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) + + if (!item) return null + + // 检查是否过期 + if (Date.now() - item.timestamp > this.cacheTime) { + this.cache.delete(key) + return null + } + + // 更新访问统计 + item.accessCount++ + item.lastAccessTime = Date.now() + + return item + } + + // 根据标签清除缓存 + 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)) + ) + + if (hasMatchingTag) { + this.cache.delete(key) + clearedCount++ + } + } + + return clearedCount + } + + // 清除当前搜索条件的缓存 + 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']) + } + + // 清空所有缓存 + clear(): void { + this.cache.clear() + } + + // 获取缓存统计信息 + 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 + } + + // 转换为人类可读的大小 + const sizeInKB = (totalSize / 1024).toFixed(2) + const avgHits = total > 0 ? (totalAccess / total).toFixed(1) : '0' + + return { + total, + size: `${sizeInKB}KB`, + hitRate: `${avgHits} avg hits` + } + } + + // 清理过期缓存 + 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++ + } + } + + return cleanedCount + } +} diff --git a/adminSystem/src/utils/table/tableConfig.ts b/adminSystem/src/utils/table/tableConfig.ts new file mode 100644 index 0000000..e464c89 --- /dev/null +++ b/adminSystem/src/utils/table/tableConfig.ts @@ -0,0 +1,55 @@ +/** + * 表格全局配置模块 + * + * 提供表格与后端接口的字段映射配置 + * + * ## 主要功能 + * + * - 响应数据字段自动识别和映射 + * - 支持多种常见的后端响应格式 + * - 请求参数字段映射配置 + * - 可扩展的字段配置机制 + * + * ## 使用场景 + * + * - 适配不同后端的分页接口格式 + * - 统一前端表格组件的数据处理 + * - 减少重复的数据转换代码 + * - 支持多个后端服务的接口对接 + * + * ## 配置说明 + * + * - recordFields: 列表数据字段名(按优先级顺序查找) + * - totalFields: 总条数字段名 + * - currentFields: 当前页码字段名 + * - sizeFields: 每页大小字段名 + * - paginationKey: 前端发送请求时使用的分页参数名 + * + * ## 扩展方式 + * + * 如果后端使用其他字段名,可以在对应数组中添加新的字段名 + * 例如:recordFields: ['list', 'data', 'records', 'items', 'yourCustomField'] + * + * @module utils/table/tableConfig + * @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'], + + // 请求参数映射配置,前端发送请求时使用的分页参数名 + // useTable 组合式函数传递分页参数的时候 用 current 跟 size + paginationKey: { + // 当前页码 + current: 'current', + // 每页大小 + size: 'size' + } +} diff --git a/adminSystem/src/utils/table/tableUtils.ts b/adminSystem/src/utils/table/tableUtils.ts new file mode 100644 index 0000000..3ca9db1 --- /dev/null +++ b/adminSystem/src/utils/table/tableUtils.ts @@ -0,0 +1,297 @@ +/** + * 表格工具函数模块 + * + * 提供表格数据处理和请求管理的核心工具函数 + * + * ## 主要功能 + * + * - 多格式 API 响应自动适配和标准化 + * - 表格数据提取和转换 + * - 分页信息自动更新和校验 + * - 智能防抖函数(支持取消和立即执行) + * - 统一的错误处理机制 + * - 嵌套数据结构解析 + * + * ## 使用场景 + * + * - useTable 组合式函数的底层工具 + * - 适配各种后端接口响应格式 + * - 表格数据的标准化处理 + * - 请求防抖和性能优化 + * - 错误统一处理和日志记录 + * + * ## 支持的响应格式 + * + * 1. 直接数组: [item1, item2, ...] + * 2. 标准对象: { records: [], total: 100 } + * 3. 嵌套data: { data: { list: [], total: 100 } } + * 4. 多种字段名: list/data/records/items/result/rows + * + * ## 核心功能 + * + * - defaultResponseAdapter: 智能识别和转换响应格式 + * - extractTableData: 提取表格数据数组 + * - updatePaginationFromResponse: 更新分页信息 + * - createSmartDebounce: 创建可控的防抖函数 + * - createErrorHandler: 生成错误处理器 + * + * @module utils/table/tableUtils + * @author Art Design Pro Team + */ + +import type { ApiResponse } from './tableCache' +import { tableConfig } from './tableConfig' + +// 请求参数基础接口,扩展分页参数 +export interface BaseRequestParams extends Api.Common.PaginationParams { + [key: string]: unknown +} + +// 错误处理接口 +export interface TableError { + 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 [] +} + +// 辅助函数:从对象中提取总数 +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 +} + +// 辅助函数:提取分页参数 +function extractPagination( + obj: Record, + data?: Record +): Pick, 'current' | 'size'> | undefined { + 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 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 +} + +/** + * 默认响应适配器 - 支持多种常见的API响应格式 + */ +export const defaultResponseAdapter = (response: unknown): ApiResponse => { + // 定义支持的字段 + const recordFields = tableConfig.recordFields + + if (!response) { + return { records: [], total: 0 } + } + + 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 } + } + + 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) + + // 如果没有找到,检查嵌套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 (!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 +} + +/** + * 从标准化的API响应中提取表格数据 + */ +export const extractTableData = (response: ApiResponse): T[] => { + const data = response.records || response.data || [] + return Array.isArray(data) ? data : [] +} + +/** + * 根据API响应更新分页信息 + */ +export const updatePaginationFromResponse = ( + pagination: Api.Common.PaginationParams, + response: ApiResponse +): void => { + pagination.total = response.total ?? pagination.total ?? 0 + + 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 + } +} + +/** + * 创建智能防抖函数 - 支持取消和立即执行 + */ +export const createSmartDebounce = Promise>( + 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 + + 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.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 +} + +/** + * 生成错误处理函数 + */ +export const createErrorHandler = ( + onError?: (error: TableError) => void, + enableLog: boolean = false +) => { + 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 + } + + 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 + } +} diff --git a/adminSystem/src/utils/ui/animation.ts b/adminSystem/src/utils/ui/animation.ts new file mode 100644 index 0000000..5efd02a --- /dev/null +++ b/adminSystem/src/utils/ui/animation.ts @@ -0,0 +1,80 @@ +/** + * 主题动画工具模块 + * + * 提供主题切换的视觉动画效果 + * + * ## 主要功能 + * + * - 基于鼠标点击位置的圆形扩散动画 + * - View Transition API 支持(现代浏览器) + * - 降级处理(不支持动画的浏览器) + * - 暗黑主题切换过渡效果 + * - 页面刷新时的主题过渡优化 + * + * ## 使用场景 + * + * - 明暗主题切换 + * - 提升用户体验的视觉反馈 + * - 页面刷新时的平滑过渡 + * + * ## 技术实现 + * + * - 使用 CSS 变量存储点击位置和半径 + * - 利用 View Transition API 实现流畅动画 + * - 通过 CSS class 控制过渡效果 + * - 自动计算最大扩散半径 + * + * @module utils/theme/animation + * @author Art Design Pro Team + */ +import { useCommon } from '@/hooks/core/useCommon' +import { useTheme } from '@/hooks/core/useTheme' +import { SystemThemeEnum } from '@/enums/appEnum' +import { useSettingStore } from '@/store/modules/setting' +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)) + + // 设置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() + } +} + +/** + * 切换主题 + */ +const toggleTheme = () => { + useTheme().switchThemeStyles(useSettingStore().systemThemeType === LIGHT ? DARK : LIGHT) + useCommon().refresh() +} + +/** + * 切换主题过渡效果 + * @param enable 是否启用过渡效果 + */ +export const toggleTransition = (enable: boolean) => { + const body = document.body + + if (enable) { + body.classList.add('theme-change') + } else { + setTimeout(() => { + body.classList.remove('theme-change') + }, 300) + } +} diff --git a/adminSystem/src/utils/ui/colors.ts b/adminSystem/src/utils/ui/colors.ts new file mode 100644 index 0000000..b4f6b77 --- /dev/null +++ b/adminSystem/src/utils/ui/colors.ts @@ -0,0 +1,273 @@ +/** + * 颜色处理工具模块 + * + * 提供完整的颜色格式转换和处理功能 + * + * ## 主要功能 + * + * - Hex 与 RGB/RGBA 格式互转 + * - 颜色混合计算 + * - 颜色变浅/变深处理 + * - Element Plus 主题色自动生成 + * - 颜色格式验证 + * - CSS 变量读取 + * - 暗黑模式颜色适配 + * + * ## 使用场景 + * + * - 主题色动态切换 + * - Element Plus 组件主题定制 + * - 颜色渐变生成 + * - 明暗主题颜色计算 + * - 颜色格式标准化 + * + * ## 核心功能 + * + * - hexToRgba: Hex 转 RGBA(支持透明度) + * - hexToRgb: Hex 转 RGB 数组 + * - rgbToHex: RGB 转 Hex + * - colourBlend: 两种颜色混合 + * - getLightColor: 生成变浅的颜色 + * - getDarkColor: 生成变深的颜色 + * - handleElementThemeColor: 处理 Element Plus 主题色 + * - setElementThemeColor: 设置完整的主题色系统 + * + * ## 支持格式 + * + * - Hex: #FFF, #FFFFFF + * - RGB: rgb(255, 255, 255) + * - RGBA: rgba(255, 255, 255, 0.5) + * + * @module utils/ui/colors + * @author Art Design Pro Team + */ +import { useSettingStore } from '@/store/modules/setting' + +/** + * 颜色转换结果接口 + */ +interface RgbaResult { + red: number + green: number + blue: number + rgba: string +} + +/** + * 获取CSS变量值(别名函数) + * @param name CSS变量名 + * @returns CSS变量值 + */ +export function getCssVar(name: string): string { + return getComputedStyle(document.documentElement).getPropertyValue(name) +} + +/** + * 验证hex颜色格式 + * @param hex hex颜色值 + * @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) +} + +/** + * 验证RGB颜色值 + * @param r 红色值 + * @param g 绿色值 + * @param b 蓝色值 + * @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) +} + +/** + * 将hex颜色转换为RGBA + * @param hex hex颜色值 (支持 #FFF 或 #FFFFFF 格式) + * @param opacity 透明度 (0-1) + * @returns 包含RGB值和RGBA字符串的对象 + */ +export function hexToRgba(hex: string, opacity: number): RgbaResult { + if (!isValidHexColor(hex)) { + throw new Error('Invalid hex color format') + } + + // 移除可能存在的 # 前缀并转换为大写 + let cleanHex = hex.trim().replace(/^#/, '').toUpperCase() + + // 如果是缩写形式(如 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)) + + // 确保 opacity 在有效范围内 + const validOpacity = Math.max(0, Math.min(1, opacity)) + + // 构建 RGBA 字符串 + const rgba = `rgba(${red}, ${green}, ${blue}, ${validOpacity.toFixed(2)})` + + return { red, green, blue, rgba } +} + +/** + * 将hex颜色转换为RGB数组 + * @param hexColor hex颜色值 + * @returns RGB数组 [r, g, b] + */ +export function hexToRgb(hexColor: string): number[] { + if (!isValidHexColor(hexColor)) { + ElMessage.warning('输入错误的hex颜色值') + throw new Error('Invalid hex color format') + } + + const cleanHex = hexColor.replace(/^#/, '') + let hex = cleanHex + + // 处理缩写形式 + 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') + } + + return hexPairs.map((hexPair) => parseInt(hexPair, 16)) +} + +/** + * 将RGB颜色转换为hex + * @param r 红色值 (0-255) + * @param g 绿色值 (0-255) + * @param b 蓝色值 (0-255) + * @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') + } + + const toHex = (value: number) => { + const hex = value.toString(16) + return hex.length === 1 ? `0${hex}` : hex + } + + return `#${toHex(r)}${toHex(g)}${toHex(b)}` +} + +/** + * 颜色混合 + * @param color1 第一个颜色 + * @param color2 第二个颜色 + * @param ratio 混合比例 (0-1) + * @returns 混合后的颜色 + */ +export function colourBlend(color1: string, color2: string, ratio: number): string { + const validRatio = Math.max(0, Math.min(1, Number(ratio))) + + 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) + }) + + return rgbToHex(blendedRgb[0], blendedRgb[1], blendedRgb[2]) +} + +/** + * 获取变浅的颜色 + * @param color 原始颜色 + * @param level 变浅程度 (0-1) + * @param isDark 是否为暗色主题 + * @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 (isDark) { + return getDarkColor(color, level) + } + + const rgb = hexToRgb(color) + const lightRgb = rgb.map((value) => Math.floor((255 - value) * level + value)) + + return rgbToHex(lightRgb[0], lightRgb[1], lightRgb[2]) +} + +/** + * 获取变深的颜色 + * @param color 原始颜色 + * @param level 变深程度 (0-1) + * @returns 变深后的颜色 + */ +export function getDarkColor(color: string, level: number): string { + 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))) + + return rgbToHex(darkRgb[0], darkRgb[1], darkRgb[2]) +} + +/** + * 处理 Element Plus 主题颜色 + * @param theme 主题颜色 + * @param isDark 是否为暗色主题 + */ +export function handleElementThemeColor(theme: string, isDark: boolean = false): void { + 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-dark-${i}`, + getDarkColor(theme, i / 10) + ) + } +} + +/** + * 设置 Element Plus 主题颜色 + * @param color 主题颜色 + */ +export function setElementThemeColor(color: string): void { + const mixColor = '#ffffff' + const elStyle = document.documentElement.style + + 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) + } +} diff --git a/adminSystem/src/utils/ui/emojo.ts b/adminSystem/src/utils/ui/emojo.ts new file mode 100644 index 0000000..cabad7d --- /dev/null +++ b/adminSystem/src/utils/ui/emojo.ts @@ -0,0 +1,24 @@ +/** + * 表情 + * 用于在消息提示的时候显示对应的表情 + * + * 用法 + * ElMessage.success(`${EmojiText[200]} 图片上传成功`) + * ElMessage.error(`${EmojiText[400]} 图片上传失败`) + * ElMessage.error(`${EmojiText[500]} 图片上传失败`) + * + * @module utils/ui/emojo + * @author Art Design Pro Team + */ + +// macos 用户 按 shift + 6 可以唤出更多表情…… +const EmojiText: { [key: string]: string } = { + '0': 'O_O', // 空 + '200': '^_^', // 成功 + '400': 'T_T', // 错误请求 + '500': 'X_X' // 服务器内部错误,无法完成请求 +} + +// const EmojiIcon = ['🟢', '🔴', '🟡 ', '🚀', '✨', '💡', '🛠️', '🔥', '🎉', '🌟', '🌈'] + +export default EmojiText diff --git a/adminSystem/src/utils/ui/iconify-loader.ts b/adminSystem/src/utils/ui/iconify-loader.ts new file mode 100644 index 0000000..035de16 --- /dev/null +++ b/adminSystem/src/utils/ui/iconify-loader.ts @@ -0,0 +1,31 @@ +/** + * 离线图标加载器 + * + * 用于在内网环境下支持 Iconify 图标的离线加载。 + * 通过预加载图标集数据,避免运行时从 CDN 获取图标。 + * + * 使用方式: + * 1. 安装所需图标集:pnpm add -D @iconify-json/[icon-set-name] + * 2. 在此文件中导入并注册图标集 + * 3. 在组件中使用: + * + * @module utils/ui/iconify-loader + * @author Art Design Pro Team + */ + +// import { addCollection } from '@iconify/vue' + +// // 导入离线图标数据 + +// // 系统必要图标库 +// import riIcons from '@iconify-json/ri/icons.json' + +// // 演示图标库(可选,生产环境可移除) +// import svgSpinners from '@iconify-json/svg-spinners/icons.json' +// import lineMd from '@iconify-json/line-md/icons.json' + +// // 注册离线图标集 + +// addCollection(riIcons) +// addCollection(svgSpinners) +// addCollection(lineMd) diff --git a/adminSystem/src/utils/ui/index.ts b/adminSystem/src/utils/ui/index.ts new file mode 100644 index 0000000..9ca1049 --- /dev/null +++ b/adminSystem/src/utils/ui/index.ts @@ -0,0 +1,11 @@ +/** + * UI 相关工具函数统一导出 + * + * @module utils/ui/index + * @author Art Design Pro Team + */ + +export * from './colors' +export * from './loading' +export * from './tabs' +export * from './emojo' diff --git a/adminSystem/src/utils/ui/loading.ts b/adminSystem/src/utils/ui/loading.ts new file mode 100644 index 0000000..6580e02 --- /dev/null +++ b/adminSystem/src/utils/ui/loading.ts @@ -0,0 +1,84 @@ +/** + * 全局 Loading 加载管理模块 + * + * 提供统一的全屏加载动画管理 + * + * ## 主要功能 + * + * - 全屏 Loading 显示和隐藏 + * - 自动适配明暗主题背景色 + * - 自定义 SVG 加载动画 + * - 单例模式防止重复创建 + * - 锁定页面交互 + * + * ## 使用场景 + * + * - 页面初始化加载 + * - 大量数据请求 + * - 路由切换过渡 + * - 异步操作等待 + * + * ## 特性 + * + * - 自动检测当前主题并应用对应背景色 + * - 使用自定义 SVG 动画(四点旋转) + * - 单例模式确保同时只有一个 Loading + * - 提供便捷的显示/隐藏方法 + * + * @module utils/ui/loading + * @author Art Design Pro Team + */ +import { fourDotsSpinnerSvg } from '@/assets/svg/loading' + +/** + * 获取当前主题对应的loading背景色 + * @returns 背景色字符串 + */ +const getLoadingBackground = (): string => { + 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' +} as const + +interface LoadingInstance { + 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 + */ + hideLoading(): void { + if (loadingInstance) { + loadingInstance.close() + loadingInstance = null + } + } +} diff --git a/adminSystem/src/utils/ui/tabs.ts b/adminSystem/src/utils/ui/tabs.ts new file mode 100644 index 0000000..5f53ea5 --- /dev/null +++ b/adminSystem/src/utils/ui/tabs.ts @@ -0,0 +1,60 @@ +/** + * 标签页布局配置模块 + * + * 提供不同标签页样式的高度和间距配置 + * + * ## 主要功能 + * + * - 多种标签页样式配置(默认、卡片、谷歌风格) + * - 标签页打开/关闭状态的高度管理 + * - 顶部间距自动计算 + * - 配置获取和默认值处理 + * + * ## 使用场景 + * + * - 工作标签页(Worktab)布局计算 + * - 页面内容区域高度调整 + * - 标签页显示/隐藏时的动画 + * - 响应式布局适配 + * + * ## 配置项说明 + * + * - openTop: 标签页显示时,内容区域距离顶部的距离 + * - closeTop: 标签页隐藏时,内容区域距离顶部的距离 + * - openHeight: 标签页显示时的总高度(包含标签栏) + * - closeHeight: 标签页隐藏时的总高度(仅头部) + * + * ## 支持的样式 + * + * - tab-default: 默认标签页样式 + * - tab-card: 卡片式标签页 + * - tab-google: 谷歌浏览器风格标签页 + * + * @module utils/ui/tabs + * @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 样式配置,设置默认值 +export const getTabConfig = (style: string) => { + return TAB_CONFIG[style as keyof typeof TAB_CONFIG] || TAB_CONFIG['tab-card'] // 默认使用 tab-card 配置 +} diff --git a/adminSystem/src/views/auth/forget-password/index.vue b/adminSystem/src/views/auth/forget-password/index.vue new file mode 100644 index 0000000..147259e --- /dev/null +++ b/adminSystem/src/views/auth/forget-password/index.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/adminSystem/src/views/auth/login/index.vue b/adminSystem/src/views/auth/login/index.vue new file mode 100644 index 0000000..7c6b4ae --- /dev/null +++ b/adminSystem/src/views/auth/login/index.vue @@ -0,0 +1,285 @@ + + + + + + + + diff --git a/adminSystem/src/views/auth/login/style.css b/adminSystem/src/views/auth/login/style.css new file mode 100644 index 0000000..bd8c3a9 --- /dev/null +++ b/adminSystem/src/views/auth/login/style.css @@ -0,0 +1,38 @@ +@reference '@styles/core/tailwind.css'; + +/* 授权页右侧区域 */ +.auth-right-wrap { + @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]; + } + + .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; + } + + .custom-height { + @apply !h-[40px]; + } +} + +/* 滑入动画 */ +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(30px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} diff --git a/adminSystem/src/views/auth/register/index.vue b/adminSystem/src/views/auth/register/index.vue new file mode 100644 index 0000000..9a8570d --- /dev/null +++ b/adminSystem/src/views/auth/register/index.vue @@ -0,0 +1,240 @@ + + + + + + diff --git a/adminSystem/src/views/dashboard/console/index.vue b/adminSystem/src/views/dashboard/console/index.vue new file mode 100644 index 0000000..154c330 --- /dev/null +++ b/adminSystem/src/views/dashboard/console/index.vue @@ -0,0 +1,41 @@ + + + + diff --git a/adminSystem/src/views/dashboard/console/modules/about-project.vue b/adminSystem/src/views/dashboard/console/modules/about-project.vue new file mode 100644 index 0000000..ed946ce --- /dev/null +++ b/adminSystem/src/views/dashboard/console/modules/about-project.vue @@ -0,0 +1,44 @@ + + + diff --git a/adminSystem/src/views/dashboard/console/modules/active-user.vue b/adminSystem/src/views/dashboard/console/modules/active-user.vue new file mode 100644 index 0000000..da740f2 --- /dev/null +++ b/adminSystem/src/views/dashboard/console/modules/active-user.vue @@ -0,0 +1,47 @@ + + + diff --git a/adminSystem/src/views/dashboard/console/modules/card-list.vue b/adminSystem/src/views/dashboard/console/modules/card-list.vue new file mode 100644 index 0000000..5fc76a7 --- /dev/null +++ b/adminSystem/src/views/dashboard/console/modules/card-list.vue @@ -0,0 +1,74 @@ + + + diff --git a/adminSystem/src/views/dashboard/console/modules/dynamic-stats.vue b/adminSystem/src/views/dashboard/console/modules/dynamic-stats.vue new file mode 100644 index 0000000..1876950 --- /dev/null +++ b/adminSystem/src/views/dashboard/console/modules/dynamic-stats.vue @@ -0,0 +1,79 @@ + + + diff --git a/adminSystem/src/views/dashboard/console/modules/new-user.vue b/adminSystem/src/views/dashboard/console/modules/new-user.vue new file mode 100644 index 0000000..9d39522 --- /dev/null +++ b/adminSystem/src/views/dashboard/console/modules/new-user.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/adminSystem/src/views/dashboard/console/modules/sales-overview.vue b/adminSystem/src/views/dashboard/console/modules/sales-overview.vue new file mode 100644 index 0000000..32904b8 --- /dev/null +++ b/adminSystem/src/views/dashboard/console/modules/sales-overview.vue @@ -0,0 +1,43 @@ + + + diff --git a/adminSystem/src/views/dashboard/console/modules/todo-list.vue b/adminSystem/src/views/dashboard/console/modules/todo-list.vue new file mode 100644 index 0000000..ab9a86c --- /dev/null +++ b/adminSystem/src/views/dashboard/console/modules/todo-list.vue @@ -0,0 +1,71 @@ + + + diff --git a/adminSystem/src/views/exception/403/index.vue b/adminSystem/src/views/exception/403/index.vue new file mode 100644 index 0000000..2756c42 --- /dev/null +++ b/adminSystem/src/views/exception/403/index.vue @@ -0,0 +1,16 @@ + + + + diff --git a/adminSystem/src/views/exception/404/index.vue b/adminSystem/src/views/exception/404/index.vue new file mode 100644 index 0000000..6b64f45 --- /dev/null +++ b/adminSystem/src/views/exception/404/index.vue @@ -0,0 +1,16 @@ + + + + diff --git a/adminSystem/src/views/exception/500/index.vue b/adminSystem/src/views/exception/500/index.vue new file mode 100644 index 0000000..1b26377 --- /dev/null +++ b/adminSystem/src/views/exception/500/index.vue @@ -0,0 +1,16 @@ + + + + diff --git a/adminSystem/src/views/index/index.vue b/adminSystem/src/views/index/index.vue new file mode 100644 index 0000000..415a436 --- /dev/null +++ b/adminSystem/src/views/index/index.vue @@ -0,0 +1,29 @@ + + + + + + diff --git a/adminSystem/src/views/index/style.scss b/adminSystem/src/views/index/style.scss new file mode 100644 index 0000000..c89f354 --- /dev/null +++ b/adminSystem/src/views/index/style.scss @@ -0,0 +1,93 @@ +.app-layout { + display: flex; + width: 100%; + min-height: 100vh; + background: var(--default-bg-color); + + #app-sidebar { + flex-shrink: 0; + } + + #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-content { + flex: 1; + + :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; + } + } + } + } +} + +@media only screen and (width <= 1180px) { + .app-layout { + #app-main { + height: 100dvh; + } + } +} + +@media only screen and (width <= 800px) { + .app-layout { + position: relative; + + #app-sidebar { + position: fixed; + top: 0; + left: 0; + z-index: 300; + height: 100vh; + } + + #app-main { + width: 100%; + height: auto; + overflow: visible; + + #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); + } + } + } + } +} diff --git a/adminSystem/src/views/outside/Iframe.vue b/adminSystem/src/views/outside/Iframe.vue new file mode 100644 index 0000000..33ea0dc --- /dev/null +++ b/adminSystem/src/views/outside/Iframe.vue @@ -0,0 +1,42 @@ + + + diff --git a/adminSystem/src/views/result/fail/index.vue b/adminSystem/src/views/result/fail/index.vue new file mode 100644 index 0000000..8fe2583 --- /dev/null +++ b/adminSystem/src/views/result/fail/index.vue @@ -0,0 +1,28 @@ + + + diff --git a/adminSystem/src/views/result/success/index.vue b/adminSystem/src/views/result/success/index.vue new file mode 100644 index 0000000..ae57aba --- /dev/null +++ b/adminSystem/src/views/result/success/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/adminSystem/src/views/system/menu/index.vue b/adminSystem/src/views/system/menu/index.vue new file mode 100644 index 0000000..973b1e7 --- /dev/null +++ b/adminSystem/src/views/system/menu/index.vue @@ -0,0 +1,479 @@ + + + + diff --git a/adminSystem/src/views/system/menu/modules/menu-dialog.vue b/adminSystem/src/views/system/menu/modules/menu-dialog.vue new file mode 100644 index 0000000..f512301 --- /dev/null +++ b/adminSystem/src/views/system/menu/modules/menu-dialog.vue @@ -0,0 +1,384 @@ + + + diff --git a/adminSystem/src/views/system/role/index.vue b/adminSystem/src/views/system/role/index.vue new file mode 100644 index 0000000..aca447e --- /dev/null +++ b/adminSystem/src/views/system/role/index.vue @@ -0,0 +1,242 @@ + + + + diff --git a/adminSystem/src/views/system/role/modules/role-edit-dialog.vue b/adminSystem/src/views/system/role/modules/role-edit-dialog.vue new file mode 100644 index 0000000..46ff9b1 --- /dev/null +++ b/adminSystem/src/views/system/role/modules/role-edit-dialog.vue @@ -0,0 +1,162 @@ + + + diff --git a/adminSystem/src/views/system/role/modules/role-permission-dialog.vue b/adminSystem/src/views/system/role/modules/role-permission-dialog.vue new file mode 100644 index 0000000..3691ac8 --- /dev/null +++ b/adminSystem/src/views/system/role/modules/role-permission-dialog.vue @@ -0,0 +1,254 @@ + + + diff --git a/adminSystem/src/views/system/role/modules/role-search.vue b/adminSystem/src/views/system/role/modules/role-search.vue new file mode 100644 index 0000000..1d59cee --- /dev/null +++ b/adminSystem/src/views/system/role/modules/role-search.vue @@ -0,0 +1,121 @@ + + + diff --git a/adminSystem/src/views/system/user-center/index.vue b/adminSystem/src/views/system/user-center/index.vue new file mode 100644 index 0000000..ab52f00 --- /dev/null +++ b/adminSystem/src/views/system/user-center/index.vue @@ -0,0 +1,247 @@ + + + + diff --git a/adminSystem/src/views/system/user/index.vue b/adminSystem/src/views/system/user/index.vue new file mode 100644 index 0000000..fb794d7 --- /dev/null +++ b/adminSystem/src/views/system/user/index.vue @@ -0,0 +1,261 @@ + + + + + + + + diff --git a/adminSystem/src/views/system/user/modules/user-dialog.vue b/adminSystem/src/views/system/user/modules/user-dialog.vue new file mode 100644 index 0000000..03cab4f --- /dev/null +++ b/adminSystem/src/views/system/user/modules/user-dialog.vue @@ -0,0 +1,143 @@ + + + diff --git a/adminSystem/src/views/system/user/modules/user-search.vue b/adminSystem/src/views/system/user/modules/user-search.vue new file mode 100644 index 0000000..e097720 --- /dev/null +++ b/adminSystem/src/views/system/user/modules/user-search.vue @@ -0,0 +1,112 @@ + + + diff --git a/adminSystem/tsconfig.json b/adminSystem/tsconfig.json new file mode 100644 index 0000000..4331962 --- /dev/null +++ b/adminSystem/tsconfig.json @@ -0,0 +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"] +} diff --git a/adminSystem/vite.config.ts b/adminSystem/vite.config.ts new file mode 100644 index 0000000..c2ef072 --- /dev/null +++ b/adminSystem/vite.config.ts @@ -0,0 +1,156 @@ +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' +import { fileURLToPath } from 'url' +import vueDevTools from 'vite-plugin-vue-devtools' +import viteCompression from 'vite-plugin-compression' +import Components from 'unplugin-vue-components/vite' +import AutoImport from 'unplugin-auto-import/vite' +import ElementPlus from 'unplugin-element-plus/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +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 + + 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: ` + @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() + } + } + } + } + ] + } + } + }) +} + +function resolvePath(paths: string) { + return path.resolve(__dirname, paths) +} diff --git a/backend-csharp/AmtScanner.Api/Controllers/RemoteDesktopController.cs b/backend-csharp/AmtScanner.Api/Controllers/RemoteDesktopController.cs index 81df433..d5c6213 100644 --- a/backend-csharp/AmtScanner.Api/Controllers/RemoteDesktopController.cs +++ b/backend-csharp/AmtScanner.Api/Controllers/RemoteDesktopController.cs @@ -1,7 +1,9 @@ -using AmtScanner.Api.Data; +using AmtScanner.Api.Data; +using AmtScanner.Api.Models; using AmtScanner.Api.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using System.Security.Cryptography; namespace AmtScanner.Api.Controllers; @@ -13,97 +15,136 @@ public class RemoteDesktopController : ControllerBase private readonly AppDbContext _context; private readonly ILogger _logger; - public RemoteDesktopController( - IGuacamoleService guacamoleService, - AppDbContext context, - ILogger logger) + public RemoteDesktopController(IGuacamoleService guacamoleService, AppDbContext context, ILogger logger) { _guacamoleService = guacamoleService; _context = context; _logger = logger; } - /// - /// 获取远程桌面连接 URL - /// + [HttpPost("generate-token/{deviceId}")] + public ActionResult GenerateToken(long deviceId, [FromBody] GenerateTokenRequest request) + { + return Ok(new { success = true, deviceId = deviceId, minutes = request.ExpiresInMinutes }); + // var device = await _context.AmtDevices.FindAsync(deviceId); + if (device == null) return NotFound(new { error = "设备不存在" }); + + WindowsCredential? credential = request.CredentialId.HasValue + ? await _context.WindowsCredentials.FindAsync(request.CredentialId.Value) + : await _context.WindowsCredentials.FirstOrDefaultAsync(c => c.IsDefault); + + if (credential == null) return BadRequest(new { error = "请先配置 Windows 凭据" }); + + var token = GenerateRandomToken(); + var expiresAt = DateTime.UtcNow.AddMinutes(request.ExpiresInMinutes ?? 30); + var accessToken = new RemoteAccessToken { Token = token, DeviceId = deviceId, WindowsCredentialId = credential.Id, ExpiresAt = expiresAt, MaxUseCount = request.MaxUseCount ?? 1, Note = request.Note }; + _context.RemoteAccessTokens.Add(accessToken); + await _context.SaveChangesAsync(); + + var baseUrl = $"{Request.Scheme}://{Request.Host}"; + return Ok(new GenerateTokenResponse { Success = true, Token = token, AccessUrl = $"{baseUrl}/remote/{token}", ExpiresAt = expiresAt, MaxUseCount = accessToken.MaxUseCount, DeviceIp = device.IpAddress }); + } + + [HttpGet("connect-by-token/{token}")] + public async Task> ConnectByToken(string token) + { + var accessToken = await _context.RemoteAccessTokens.Include(t => t.Device).Include(t => t.WindowsCredential).FirstOrDefaultAsync(t => t.Token == token); + if (accessToken == null) return NotFound(new { error = "无效的访问链接" }); + if (!accessToken.IsValid()) return BadRequest(new { error = "访问链接已过期或已达到使用次数上限" }); + if (accessToken.Device == null || accessToken.WindowsCredential == null) return BadRequest(new { error = "设备或凭据信息不完整" }); + + accessToken.UseCount++; + accessToken.UsedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(); + + var guacToken = await _guacamoleService.GetAuthTokenAsync(); + if (string.IsNullOrEmpty(guacToken)) return StatusCode(503, new { error = "无法连接到 Guacamole 服务" }); + + var connectionId = await _guacamoleService.CreateOrGetConnectionAsync(guacToken, $"AMT-{accessToken.Device.IpAddress}", accessToken.Device.IpAddress, accessToken.WindowsCredential.Username, accessToken.WindowsCredential.Password); + if (string.IsNullOrEmpty(connectionId)) return StatusCode(500, new { error = "创建远程连接失败" }); + + var connectionUrl = await _guacamoleService.GetConnectionUrlAsync(guacToken, connectionId); + return Ok(new RemoteDesktopResponse { Success = true, ConnectionUrl = connectionUrl, ConnectionId = connectionId, Token = guacToken, DeviceIp = accessToken.Device.IpAddress }); + } + + [HttpGet("validate-token/{token}")] + public async Task> ValidateToken(string token) + { + var accessToken = await _context.RemoteAccessTokens.Include(t => t.Device).FirstOrDefaultAsync(t => t.Token == token); + if (accessToken == null) return Ok(new ValidateTokenResponse { Valid = false, Error = "无效的访问链接" }); + if (!accessToken.IsValid()) return Ok(new ValidateTokenResponse { Valid = false, Error = "访问链接已过期或已达到使用次数上限" }); + return Ok(new ValidateTokenResponse { Valid = true, DeviceIp = accessToken.Device?.IpAddress, ExpiresAt = accessToken.ExpiresAt, RemainingUses = accessToken.MaxUseCount > 0 ? accessToken.MaxUseCount - accessToken.UseCount : -1 }); + } + + [HttpGet("list-tokens/{deviceId}")] + public async Task>> GetDeviceTokens(long deviceId) + { + var tokens = await _context.RemoteAccessTokens.Where(t => t.DeviceId == deviceId && t.ExpiresAt > DateTime.UtcNow).OrderByDescending(t => t.CreatedAt) + .Select(t => new TokenInfoDto { Id = t.Id, Token = t.Token, CreatedAt = t.CreatedAt, ExpiresAt = t.ExpiresAt, MaxUseCount = t.MaxUseCount, UseCount = t.UseCount, Note = t.Note }).ToListAsync(); + return Ok(tokens); + } + + [HttpDelete("revoke-token/{tokenId}")] + public async Task RevokeToken(long tokenId) + { + var token = await _context.RemoteAccessTokens.FindAsync(tokenId); + if (token == null) return NotFound(new { error = "Token 不存在" }); + _context.RemoteAccessTokens.Remove(token); + await _context.SaveChangesAsync(); + return Ok(new { success = true }); + } + + [HttpPost("cleanup-tokens")] + public async Task CleanupExpiredTokens() + { + var count = await _context.RemoteAccessTokens.Where(t => t.ExpiresAt < DateTime.UtcNow).ExecuteDeleteAsync(); + return Ok(new { success = true, deletedCount = count }); + } + [HttpPost("connect/{deviceId}")] public async Task> Connect(long deviceId, [FromBody] RdpCredentials credentials) { var device = await _context.AmtDevices.FindAsync(deviceId); - if (device == null) - { - return NotFound(new { error = "设备不存在" }); - } + if (device == null) return NotFound(new { error = "设备不存在" }); - // 获取 Guacamole Token - var token = await _guacamoleService.GetAuthTokenAsync(); - if (string.IsNullOrEmpty(token)) - { - return StatusCode(503, new { error = "无法连接到 Guacamole 服务,请确保 Guacamole 已启动" }); - } + var guacToken = await _guacamoleService.GetAuthTokenAsync(); + if (string.IsNullOrEmpty(guacToken)) return StatusCode(503, new { error = "无法连接到 Guacamole 服务" }); - // 创建或获取连接 - var connectionName = $"AMT-{device.IpAddress}"; - var connectionId = await _guacamoleService.CreateOrGetConnectionAsync( - token, - connectionName, - device.IpAddress, - credentials.Username, - credentials.Password); + var connectionId = await _guacamoleService.CreateOrGetConnectionAsync(guacToken, $"AMT-{device.IpAddress}", device.IpAddress, credentials.Username, credentials.Password); + if (string.IsNullOrEmpty(connectionId)) return StatusCode(500, new { error = "创建远程连接失败" }); - if (string.IsNullOrEmpty(connectionId)) - { - return StatusCode(500, new { error = "创建远程连接失败" }); - } - - // 获取连接 URL - var connectionUrl = await _guacamoleService.GetConnectionUrlAsync(token, connectionId); - - _logger.LogInformation("Created remote desktop connection for device {Ip}, connectionId: {Id}", - device.IpAddress, connectionId); - - return Ok(new RemoteDesktopResponse - { - Success = true, - ConnectionUrl = connectionUrl, - ConnectionId = connectionId, - Token = token - }); + var connectionUrl = await _guacamoleService.GetConnectionUrlAsync(guacToken, connectionId); + return Ok(new RemoteDesktopResponse { Success = true, ConnectionUrl = connectionUrl, ConnectionId = connectionId, Token = guacToken }); + } + + [HttpPost("test-post/{id}")] + public ActionResult TestPost(long id, [FromBody] GenerateTokenRequest request) + { + return Ok(new { success = true, id = id, minutes = request.ExpiresInMinutes }); } - /// - /// 测试 Guacamole 连接 - /// [HttpGet("test")] public async Task TestConnection() { var token = await _guacamoleService.GetAuthTokenAsync(); - if (string.IsNullOrEmpty(token)) - { - return StatusCode(503, new { - success = false, - error = "无法连接到 Guacamole 服务" - }); - } + if (string.IsNullOrEmpty(token)) return StatusCode(503, new { success = false, error = "无法连接到 Guacamole 服务" }); + return Ok(new { success = true, message = "Guacamole 服务连接正常" }); + } - return Ok(new { - success = true, - message = "Guacamole 服务连接正常" - }); + private static string GenerateRandomToken() + { + var bytes = new byte[24]; + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(bytes); + return Convert.ToBase64String(bytes).Replace("+", "-").Replace("/", "_").Replace("=", ""); } } -public class RdpCredentials -{ - public string Username { get; set; } = string.Empty; - public string Password { get; set; } = string.Empty; -} +public class GenerateTokenRequest { public long? CredentialId { get; set; } public int? ExpiresInMinutes { get; set; } = 30; public int? MaxUseCount { get; set; } = 1; public string? Note { get; set; } } +public class GenerateTokenResponse { public bool Success { get; set; } public string Token { get; set; } = ""; public string AccessUrl { get; set; } = ""; public DateTime ExpiresAt { get; set; } public int MaxUseCount { get; set; } public string? DeviceIp { get; set; } public string? Error { get; set; } } +public class ValidateTokenResponse { public bool Valid { get; set; } public string? DeviceIp { get; set; } public DateTime? ExpiresAt { get; set; } public int RemainingUses { get; set; } public string? Error { get; set; } } +public class TokenInfoDto { public long Id { get; set; } public string Token { get; set; } = ""; public DateTime CreatedAt { get; set; } public DateTime ExpiresAt { get; set; } public int MaxUseCount { get; set; } public int UseCount { get; set; } public string? Note { get; set; } } +public class RdpCredentials { public string Username { get; set; } = ""; public string Password { get; set; } = ""; } +public class RemoteDesktopResponse { public bool Success { get; set; } public string? ConnectionUrl { get; set; } public string? ConnectionId { get; set; } public string? Token { get; set; } public string? DeviceIp { get; set; } public string? Error { get; set; } } + -public class RemoteDesktopResponse -{ - public bool Success { get; set; } - public string? ConnectionUrl { get; set; } - public string? ConnectionId { get; set; } - public string? Token { get; set; } - public string? Error { get; set; } -} diff --git a/backend-csharp/AmtScanner.Api/Controllers/WindowsCredentialsController.cs b/backend-csharp/AmtScanner.Api/Controllers/WindowsCredentialsController.cs new file mode 100644 index 0000000..e60d265 --- /dev/null +++ b/backend-csharp/AmtScanner.Api/Controllers/WindowsCredentialsController.cs @@ -0,0 +1,195 @@ +using AmtScanner.Api.Data; +using AmtScanner.Api.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace AmtScanner.Api.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class WindowsCredentialsController : ControllerBase +{ + private readonly AppDbContext _context; + private readonly ILogger _logger; + + public WindowsCredentialsController(AppDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + /// + /// 获取所有 Windows 凭据 + /// + [HttpGet] + public async Task>> GetAll() + { + var credentials = await _context.WindowsCredentials + .OrderByDescending(c => c.IsDefault) + .ThenBy(c => c.Name) + .Select(c => new WindowsCredentialDto + { + Id = c.Id, + Name = c.Name, + Username = c.Username, + Domain = c.Domain, + IsDefault = c.IsDefault, + Note = c.Note, + CreatedAt = c.CreatedAt + }) + .ToListAsync(); + + return Ok(credentials); + } + + /// + /// 创建 Windows 凭据 + /// + [HttpPost] + public async Task> Create([FromBody] CreateWindowsCredentialRequest request) + { + // 如果设为默认,取消其他默认 + if (request.IsDefault) + { + await _context.WindowsCredentials + .Where(c => c.IsDefault) + .ExecuteUpdateAsync(s => s.SetProperty(c => c.IsDefault, false)); + } + + var credential = new WindowsCredential + { + Name = request.Name, + Username = request.Username, + Password = request.Password, // 实际生产环境应加密 + Domain = request.Domain, + IsDefault = request.IsDefault, + Note = request.Note + }; + + _context.WindowsCredentials.Add(credential); + await _context.SaveChangesAsync(); + + _logger.LogInformation("Created Windows credential: {Name}", credential.Name); + + return Ok(new WindowsCredentialDto + { + Id = credential.Id, + Name = credential.Name, + Username = credential.Username, + Domain = credential.Domain, + IsDefault = credential.IsDefault, + Note = credential.Note, + CreatedAt = credential.CreatedAt + }); + } + + /// + /// 更新 Windows 凭据 + /// + [HttpPut("{id}")] + public async Task Update(long id, [FromBody] UpdateWindowsCredentialRequest request) + { + var credential = await _context.WindowsCredentials.FindAsync(id); + if (credential == null) + { + return NotFound(new { error = "凭据不存在" }); + } + + // 如果设为默认,取消其他默认 + if (request.IsDefault && !credential.IsDefault) + { + await _context.WindowsCredentials + .Where(c => c.IsDefault && c.Id != id) + .ExecuteUpdateAsync(s => s.SetProperty(c => c.IsDefault, false)); + } + + credential.Name = request.Name; + credential.Username = request.Username; + if (!string.IsNullOrEmpty(request.Password)) + { + credential.Password = request.Password; + } + credential.Domain = request.Domain; + credential.IsDefault = request.IsDefault; + credential.Note = request.Note; + credential.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + + return Ok(new { success = true }); + } + + /// + /// 删除 Windows 凭据 + /// + [HttpDelete("{id}")] + public async Task Delete(long id) + { + var credential = await _context.WindowsCredentials.FindAsync(id); + if (credential == null) + { + return NotFound(new { error = "凭据不存在" }); + } + + _context.WindowsCredentials.Remove(credential); + await _context.SaveChangesAsync(); + + _logger.LogInformation("Deleted Windows credential: {Name}", credential.Name); + + return Ok(new { success = true }); + } + + /// + /// 设置默认凭据 + /// + [HttpPost("{id}/set-default")] + public async Task SetDefault(long id) + { + var credential = await _context.WindowsCredentials.FindAsync(id); + if (credential == null) + { + return NotFound(new { error = "凭据不存在" }); + } + + // 取消其他默认 + await _context.WindowsCredentials + .Where(c => c.IsDefault) + .ExecuteUpdateAsync(s => s.SetProperty(c => c.IsDefault, false)); + + credential.IsDefault = true; + await _context.SaveChangesAsync(); + + return Ok(new { success = true }); + } +} + +public class WindowsCredentialDto +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string? Domain { get; set; } + public bool IsDefault { get; set; } + public string? Note { get; set; } + public DateTime CreatedAt { get; set; } +} + +public class CreateWindowsCredentialRequest +{ + public string Name { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public string? Domain { get; set; } + public bool IsDefault { get; set; } + public string? Note { get; set; } +} + +public class UpdateWindowsCredentialRequest +{ + public string Name { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string? Password { get; set; } + public string? Domain { get; set; } + public bool IsDefault { get; set; } + public string? Note { get; set; } +} diff --git a/backend-csharp/AmtScanner.Api/Data/AppDbContext.cs b/backend-csharp/AmtScanner.Api/Data/AppDbContext.cs index d941606..fb1edcc 100644 --- a/backend-csharp/AmtScanner.Api/Data/AppDbContext.cs +++ b/backend-csharp/AmtScanner.Api/Data/AppDbContext.cs @@ -14,6 +14,8 @@ public class AppDbContext : DbContext public DbSet HardwareInfos { get; set; } public DbSet MemoryModules { get; set; } public DbSet StorageDevices { get; set; } + public DbSet WindowsCredentials { get; set; } + public DbSet RemoteAccessTokens { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -61,5 +63,34 @@ public class AppDbContext : DbContext .WithMany(h => h.StorageDevices) .HasForeignKey(s => s.HardwareInfoId) .OnDelete(DeleteBehavior.Cascade); + + // WindowsCredential 配置 + modelBuilder.Entity() + .Property(w => w.Name) + .HasMaxLength(200); + + modelBuilder.Entity() + .HasIndex(w => w.Name); + + // RemoteAccessToken 配置 + modelBuilder.Entity() + .Property(t => t.Token) + .HasMaxLength(64); + + modelBuilder.Entity() + .HasIndex(t => t.Token) + .IsUnique(); + + modelBuilder.Entity() + .HasOne(t => t.Device) + .WithMany() + .HasForeignKey(t => t.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder.Entity() + .HasOne(t => t.WindowsCredential) + .WithMany() + .HasForeignKey(t => t.WindowsCredentialId) + .OnDelete(DeleteBehavior.SetNull); } } diff --git a/backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.Designer.cs b/backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.Designer.cs new file mode 100644 index 0000000..c20cb15 --- /dev/null +++ b/backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.Designer.cs @@ -0,0 +1,384 @@ +// +using System; +using AmtScanner.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace AmtScanner.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260120053346_AddWindowsCredentialsAndRemoteAccessTokens")] + partial class AddWindowsCredentialsAndRemoteAccessTokens + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("AmtScanner.Api.Models.AmtCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("AmtCredentials"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.AmtDevice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AmtOnline") + .HasColumnType("tinyint(1)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("DiscoveredAt") + .HasColumnType("datetime(6)"); + + b.Property("Hostname") + .HasColumnType("longtext"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LastSeenAt") + .HasColumnType("datetime(6)"); + + b.Property("MajorVersion") + .HasColumnType("int"); + + b.Property("MinorVersion") + .HasColumnType("int"); + + b.Property("OsOnline") + .HasColumnType("tinyint(1)"); + + b.Property("ProvisioningState") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IpAddress") + .IsUnique(); + + b.ToTable("AmtDevices"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeviceId") + .HasColumnType("bigint"); + + b.Property("LastUpdated") + .HasColumnType("datetime(6)"); + + b.Property("ProcessorCores") + .HasColumnType("int"); + + b.Property("ProcessorCurrentClockSpeed") + .HasColumnType("int"); + + b.Property("ProcessorMaxClockSpeed") + .HasColumnType("int"); + + b.Property("ProcessorModel") + .HasColumnType("longtext"); + + b.Property("ProcessorThreads") + .HasColumnType("int"); + + b.Property("SystemManufacturer") + .HasColumnType("longtext"); + + b.Property("SystemModel") + .HasColumnType("longtext"); + + b.Property("SystemSerialNumber") + .HasColumnType("longtext"); + + b.Property("TotalMemoryBytes") + .HasColumnType("bigint"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("LastUpdated"); + + b.ToTable("HardwareInfos"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.MemoryModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CapacityBytes") + .HasColumnType("bigint"); + + b.Property("HardwareInfoId") + .HasColumnType("bigint"); + + b.Property("Manufacturer") + .HasColumnType("longtext"); + + b.Property("MemoryType") + .HasColumnType("longtext"); + + b.Property("PartNumber") + .HasColumnType("longtext"); + + b.Property("SerialNumber") + .HasColumnType("longtext"); + + b.Property("SlotLocation") + .HasColumnType("longtext"); + + b.Property("SpeedMHz") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("HardwareInfoId"); + + b.ToTable("MemoryModules"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeviceId") + .HasColumnType("bigint"); + + b.Property("ExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("IsUsed") + .HasColumnType("tinyint(1)"); + + b.Property("MaxUseCount") + .HasColumnType("int"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("UseCount") + .HasColumnType("int"); + + b.Property("UsedAt") + .HasColumnType("datetime(6)"); + + b.Property("WindowsCredentialId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("Token") + .IsUnique(); + + b.HasIndex("WindowsCredentialId"); + + b.ToTable("RemoteAccessTokens"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CapacityBytes") + .HasColumnType("bigint"); + + b.Property("DeviceId") + .HasColumnType("longtext"); + + b.Property("HardwareInfoId") + .HasColumnType("bigint"); + + b.Property("InterfaceType") + .HasColumnType("longtext"); + + b.Property("Model") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("HardwareInfoId"); + + b.ToTable("StorageDevices"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.WindowsCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Domain") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("WindowsCredentials"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b => + { + b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.MemoryModule", b => + { + b.HasOne("AmtScanner.Api.Models.HardwareInfo", "HardwareInfo") + .WithMany("MemoryModules") + .HasForeignKey("HardwareInfoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HardwareInfo"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b => + { + b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AmtScanner.Api.Models.WindowsCredential", "WindowsCredential") + .WithMany() + .HasForeignKey("WindowsCredentialId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Device"); + + b.Navigation("WindowsCredential"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b => + { + b.HasOne("AmtScanner.Api.Models.HardwareInfo", "HardwareInfo") + .WithMany("StorageDevices") + .HasForeignKey("HardwareInfoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HardwareInfo"); + }); + + modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b => + { + b.Navigation("MemoryModules"); + + b.Navigation("StorageDevices"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.cs b/backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.cs new file mode 100644 index 0000000..d47c026 --- /dev/null +++ b/backend-csharp/AmtScanner.Api/Migrations/20260120053346_AddWindowsCredentialsAndRemoteAccessTokens.cs @@ -0,0 +1,110 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AmtScanner.Api.Migrations +{ + /// + public partial class AddWindowsCredentialsAndRemoteAccessTokens : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "WindowsCredentials", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Username = table.Column(type: "varchar(200)", maxLength: 200, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Password = table.Column(type: "varchar(500)", maxLength: 500, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Domain = table.Column(type: "varchar(200)", maxLength: 200, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + IsDefault = table.Column(type: "tinyint(1)", nullable: false), + Note = table.Column(type: "varchar(500)", maxLength: 500, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + UpdatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WindowsCredentials", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "RemoteAccessTokens", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Token = table.Column(type: "varchar(64)", maxLength: 64, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + DeviceId = table.Column(type: "bigint", nullable: false), + WindowsCredentialId = table.Column(type: "bigint", nullable: true), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + ExpiresAt = table.Column(type: "datetime(6)", nullable: false), + IsUsed = table.Column(type: "tinyint(1)", nullable: false), + UsedAt = table.Column(type: "datetime(6)", nullable: true), + MaxUseCount = table.Column(type: "int", nullable: false), + UseCount = table.Column(type: "int", nullable: false), + Note = table.Column(type: "varchar(500)", maxLength: 500, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_RemoteAccessTokens", x => x.Id); + table.ForeignKey( + name: "FK_RemoteAccessTokens_AmtDevices_DeviceId", + column: x => x.DeviceId, + principalTable: "AmtDevices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RemoteAccessTokens_WindowsCredentials_WindowsCredentialId", + column: x => x.WindowsCredentialId, + principalTable: "WindowsCredentials", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_RemoteAccessTokens_DeviceId", + table: "RemoteAccessTokens", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_RemoteAccessTokens_Token", + table: "RemoteAccessTokens", + column: "Token", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RemoteAccessTokens_WindowsCredentialId", + table: "RemoteAccessTokens", + column: "WindowsCredentialId"); + + migrationBuilder.CreateIndex( + name: "IX_WindowsCredentials_Name", + table: "WindowsCredentials", + column: "Name"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RemoteAccessTokens"); + + migrationBuilder.DropTable( + name: "WindowsCredentials"); + } + } +} diff --git a/backend-csharp/AmtScanner.Api/Migrations/AppDbContextModelSnapshot.cs b/backend-csharp/AmtScanner.Api/Migrations/AppDbContextModelSnapshot.cs index ad07ed2..9b972b4 100644 --- a/backend-csharp/AmtScanner.Api/Migrations/AppDbContextModelSnapshot.cs +++ b/backend-csharp/AmtScanner.Api/Migrations/AppDbContextModelSnapshot.cs @@ -194,6 +194,57 @@ namespace AmtScanner.Api.Migrations b.ToTable("MemoryModules"); }); + modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeviceId") + .HasColumnType("bigint"); + + b.Property("ExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("IsUsed") + .HasColumnType("tinyint(1)"); + + b.Property("MaxUseCount") + .HasColumnType("int"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("UseCount") + .HasColumnType("int"); + + b.Property("UsedAt") + .HasColumnType("datetime(6)"); + + b.Property("WindowsCredentialId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("Token") + .IsUnique(); + + b.HasIndex("WindowsCredentialId"); + + b.ToTable("RemoteAccessTokens"); + }); + modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b => { b.Property("Id") @@ -222,6 +273,51 @@ namespace AmtScanner.Api.Migrations b.ToTable("StorageDevices"); }); + modelBuilder.Entity("AmtScanner.Api.Models.WindowsCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Domain") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("IsDefault") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("WindowsCredentials"); + }); + modelBuilder.Entity("AmtScanner.Api.Models.HardwareInfo", b => { b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device") @@ -244,6 +340,24 @@ namespace AmtScanner.Api.Migrations b.Navigation("HardwareInfo"); }); + modelBuilder.Entity("AmtScanner.Api.Models.RemoteAccessToken", b => + { + b.HasOne("AmtScanner.Api.Models.AmtDevice", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("AmtScanner.Api.Models.WindowsCredential", "WindowsCredential") + .WithMany() + .HasForeignKey("WindowsCredentialId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Device"); + + b.Navigation("WindowsCredential"); + }); + modelBuilder.Entity("AmtScanner.Api.Models.StorageDevice", b => { b.HasOne("AmtScanner.Api.Models.HardwareInfo", "HardwareInfo") diff --git a/backend-csharp/AmtScanner.Api/Models/RemoteAccessToken.cs b/backend-csharp/AmtScanner.Api/Models/RemoteAccessToken.cs new file mode 100644 index 0000000..7a7b1e9 --- /dev/null +++ b/backend-csharp/AmtScanner.Api/Models/RemoteAccessToken.cs @@ -0,0 +1,85 @@ +using System.ComponentModel.DataAnnotations; + +namespace AmtScanner.Api.Models; + +/// +/// 远程访问临时 Token +/// +public class RemoteAccessToken +{ + [Key] + public long Id { get; set; } + + /// + /// 唯一 Token 字符串 + /// + [Required] + [MaxLength(64)] + public string Token { get; set; } = string.Empty; + + /// + /// 关联的设备 ID + /// + public long DeviceId { get; set; } + + /// + /// 关联的设备 + /// + public AmtDevice? Device { get; set; } + + /// + /// 关联的 Windows 凭据 ID + /// + public long? WindowsCredentialId { get; set; } + + /// + /// 关联的 Windows 凭据 + /// + public WindowsCredential? WindowsCredential { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// + /// 过期时间 + /// + public DateTime ExpiresAt { get; set; } + + /// + /// 是否已使用(一次性 Token) + /// + public bool IsUsed { get; set; } = false; + + /// + /// 使用时间 + /// + public DateTime? UsedAt { get; set; } + + /// + /// 最大使用次数(0 表示无限制) + /// + public int MaxUseCount { get; set; } = 1; + + /// + /// 已使用次数 + /// + public int UseCount { get; set; } = 0; + + /// + /// 创建者备注 + /// + [MaxLength(500)] + public string? Note { get; set; } + + /// + /// 检查 Token 是否有效 + /// + public bool IsValid() + { + if (DateTime.UtcNow > ExpiresAt) return false; + if (MaxUseCount > 0 && UseCount >= MaxUseCount) return false; + return true; + } +} diff --git a/backend-csharp/AmtScanner.Api/Models/WindowsCredential.cs b/backend-csharp/AmtScanner.Api/Models/WindowsCredential.cs new file mode 100644 index 0000000..0edd7cf --- /dev/null +++ b/backend-csharp/AmtScanner.Api/Models/WindowsCredential.cs @@ -0,0 +1,60 @@ +using System.ComponentModel.DataAnnotations; + +namespace AmtScanner.Api.Models; + +/// +/// Windows 远程桌面凭据 +/// +public class WindowsCredential +{ + [Key] + public long Id { get; set; } + + /// + /// 凭据名称(便于识别) + /// + [Required] + [MaxLength(200)] + public string Name { get; set; } = string.Empty; + + /// + /// Windows 用户名 + /// + [Required] + [MaxLength(200)] + public string Username { get; set; } = string.Empty; + + /// + /// Windows 密码(加密存储) + /// + [Required] + [MaxLength(500)] + public string Password { get; set; } = string.Empty; + + /// + /// 域名(可选) + /// + [MaxLength(200)] + public string? Domain { get; set; } + + /// + /// 是否为默认凭据 + /// + public bool IsDefault { get; set; } = false; + + /// + /// 备注 + /// + [MaxLength(500)] + public string? Note { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// + /// 更新时间 + /// + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/backend-csharp/AmtScanner.Api/Program.cs b/backend-csharp/AmtScanner.Api/Program.cs index a47c6f2..eca59a8 100644 --- a/backend-csharp/AmtScanner.Api/Program.cs +++ b/backend-csharp/AmtScanner.Api/Program.cs @@ -83,7 +83,27 @@ using (var scope = app.Services.CreateScope()) try { // Apply migrations (create database and tables) - db.Database.Migrate(); + // 如果迁移失败(表已存在),尝试手动标记迁移为已完成 + try + { + db.Database.Migrate(); + } + catch (Exception migrationEx) when (migrationEx.Message.Contains("already exists")) + { + Console.WriteLine("⚠️ 表已存在,尝试标记迁移为已完成..."); + var pendingMigrations = db.Database.GetPendingMigrations().ToList(); + foreach (var migration in pendingMigrations) + { + try + { + db.Database.ExecuteSqlRaw( + "INSERT IGNORE INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) VALUES ({0}, {1})", + migration, "8.0.0"); + Console.WriteLine($"✅ 已标记迁移: {migration}"); + } + catch { } + } + } // Add default credential if not exists if (!db.AmtCredentials.Any()) diff --git a/backend-csharp/AmtScanner.Api/fix-migration.sql b/backend-csharp/AmtScanner.Api/fix-migration.sql new file mode 100644 index 0000000..a38898b --- /dev/null +++ b/backend-csharp/AmtScanner.Api/fix-migration.sql @@ -0,0 +1,7 @@ +-- 如果表已存在,只需要插入迁移记录 +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +SELECT '20260120053346_AddWindowsCredentialsAndRemoteAccessTokens', '8.0.0' +WHERE NOT EXISTS ( + SELECT 1 FROM `__EFMigrationsHistory` + WHERE `MigrationId` = '20260120053346_AddWindowsCredentialsAndRemoteAccessTokens' +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2725955..21a2fc9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,7 +12,8 @@ "axios": "^1.6.0", "element-plus": "^2.5.0", "pinia": "^2.1.0", - "vue": "^3.4.0" + "vue": "^3.4.0", + "vue-router": "^4.6.4" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.0", @@ -1875,6 +1876,21 @@ } } }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 250372f..2d0caf1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,11 +8,12 @@ "preview": "vite preview" }, "dependencies": { - "vue": "^3.4.0", + "@microsoft/signalr": "^8.0.0", "axios": "^1.6.0", "element-plus": "^2.5.0", "pinia": "^2.1.0", - "@microsoft/signalr": "^8.0.0" + "vue": "^3.4.0", + "vue-router": "^4.6.4" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.0", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index ca7f310..67f1bcb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,109 +1,8 @@ diff --git a/frontend/src/components/RemoteDesktopModal.vue b/frontend/src/components/RemoteDesktopModal.vue index c25b760..fe84547 100644 --- a/frontend/src/components/RemoteDesktopModal.vue +++ b/frontend/src/components/RemoteDesktopModal.vue @@ -1,4 +1,4 @@ -