diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4515e10 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,31 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Matches multiple files with brace expansion notation +[*.{js,jsx,ts,tsx,vue}] +indent_style = space +indent_size = 2 + +# Matches multiple files with brace expansion notation +[*.{json,md,yml,yaml}] +indent_style = space +indent_size = 2 + +# Matches multiple files with brace expansion notation +[*.scss] +indent_style = space +indent_size = 2 + +# The above files are for code, but for config files we want no indentation +[{package.json,.eslintrc.cjs,.prettierrc}] +indent_style = space +indent_size = 2 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..21f91bd --- /dev/null +++ b/.env.development @@ -0,0 +1,3 @@ +# 开发环境配置 +VITE_API_BASE_URL=http://localhost:8000/api/v1 +VITE_APP_TITLE=资产管理系统 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..e693833 --- /dev/null +++ b/.env.production @@ -0,0 +1,3 @@ +# 生产环境配置 +VITE_API_BASE_URL=https://zc.workyai.cn/api/v1 +VITE_APP_TITLE=资产管理系统 diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json new file mode 100644 index 0000000..273d4ac --- /dev/null +++ b/.eslintrc-auto-import.json @@ -0,0 +1,312 @@ +{ + "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, + "DirectiveBinding": true, + "EffectScope": true, + "ExtractDefaultPropTypes": true, + "ExtractPropTypes": true, + "ExtractPublicPropTypes": true, + "InjectionKey": true, + "MaybeRef": true, + "MaybeRefOrGetter": true, + "PropType": true, + "Ref": true, + "VNode": true, + "WritableComputedRef": true, + "acceptHMRUpdate": true, + "asyncComputed": true, + "autoResetRef": true, + "computed": true, + "computedAsync": true, + "computedEager": true, + "computedInject": true, + "computedWithControl": true, + "controlledComputed": true, + "controlledRef": true, + "createApp": true, + "createEventHook": true, + "createGlobalState": true, + "createInjectionState": true, + "createPinia": true, + "createReactiveFn": true, + "createReusableTemplate": true, + "createSharedComposable": true, + "createTemplatePromise": true, + "createUnrefFn": true, + "customRef": true, + "debouncedRef": true, + "debouncedWatch": true, + "defineAsyncComponent": true, + "defineComponent": true, + "defineStore": true, + "eagerComputed": true, + "effectScope": true, + "extendRef": true, + "getActivePinia": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "ignorableWatch": true, + "inject": true, + "injectLocal": true, + "isDefined": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "makeDestructurable": true, + "mapActions": true, + "mapGetters": true, + "mapState": true, + "mapStores": true, + "mapWritableState": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeRouteLeave": true, + "onBeforeRouteUpdate": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onClickOutside": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onKeyStroke": true, + "onLongPress": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onStartTyping": true, + "onUnmounted": true, + "onUpdated": true, + "onWatcherCleanup": true, + "pausableWatch": true, + "provide": true, + "provideLocal": true, + "reactify": true, + "reactifyObject": true, + "reactive": true, + "reactiveComputed": true, + "reactiveOmit": true, + "reactivePick": true, + "readonly": true, + "ref": true, + "refAutoReset": true, + "refDebounced": true, + "refDefault": true, + "refThrottled": true, + "refWithControl": true, + "resolveComponent": true, + "resolveRef": true, + "resolveUnref": true, + "setActivePinia": true, + "setMapStoreSuffix": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "storeToRefs": true, + "syncRef": true, + "syncRefs": true, + "templateRef": true, + "throttledRef": true, + "throttledWatch": true, + "toRaw": true, + "toReactive": true, + "toRef": true, + "toRefs": true, + "toValue": true, + "triggerRef": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unref": true, + "unrefElement": true, + "until": true, + "useActiveElement": true, + "useAnimate": true, + "useArrayDifference": true, + "useArrayEvery": true, + "useArrayFilter": true, + "useArrayFind": true, + "useArrayFindIndex": true, + "useArrayFindLast": true, + "useArrayIncludes": true, + "useArrayJoin": true, + "useArrayMap": true, + "useArrayReduce": true, + "useArraySome": true, + "useArrayUnique": true, + "useAsyncQueue": true, + "useAsyncState": true, + "useAttrs": true, + "useBase64": true, + "useBattery": true, + "useBluetooth": true, + "useBreakpoints": true, + "useBroadcastChannel": true, + "useBrowserLocation": true, + "useCached": true, + "useClipboard": true, + "useClipboardItems": true, + "useCloned": true, + "useColorMode": true, + "useConfirmDialog": true, + "useCounter": true, + "useCssModule": true, + "useCssVar": true, + "useCssVars": true, + "useCurrentElement": true, + "useCycleList": true, + "useDark": true, + "useDateFormat": true, + "useDebounce": true, + "useDebounceFn": true, + "useDebouncedRefHistory": true, + "useDeviceMotion": true, + "useDeviceOrientation": true, + "useDevicePixelRatio": true, + "useDevicesList": true, + "useDisplayMedia": true, + "useDocumentVisibility": true, + "useDraggable": true, + "useDropZone": true, + "useElementBounding": true, + "useElementByPoint": true, + "useElementHover": true, + "useElementSize": true, + "useElementVisibility": true, + "useEventBus": true, + "useEventListener": true, + "useEventSource": true, + "useEyeDropper": true, + "useFavicon": true, + "useFetch": true, + "useFileDialog": true, + "useFileSystemAccess": true, + "useFocus": true, + "useFocusWithin": true, + "useFps": true, + "useFullscreen": true, + "useGamepad": true, + "useGeolocation": true, + "useId": true, + "useIdle": true, + "useImage": true, + "useInfiniteScroll": true, + "useIntersectionObserver": true, + "useInterval": true, + "useIntervalFn": true, + "useKeyModifier": true, + "useLastChanged": true, + "useLink": true, + "useLocalStorage": true, + "useMagicKeys": true, + "useManualRefHistory": true, + "useMediaControls": true, + "useMediaQuery": true, + "useMemoize": true, + "useMemory": true, + "useModel": true, + "useMounted": true, + "useMouse": true, + "useMouseInElement": true, + "useMousePressed": true, + "useMutationObserver": true, + "useNavigatorLanguage": true, + "useNetwork": true, + "useNow": true, + "useObjectUrl": true, + "useOffsetPagination": true, + "useOnline": true, + "usePageLeave": true, + "useParallax": true, + "useParentElement": true, + "usePerformanceObserver": true, + "usePermission": true, + "usePointer": true, + "usePointerLock": true, + "usePointerSwipe": true, + "usePreferredColorScheme": true, + "usePreferredContrast": true, + "usePreferredDark": true, + "usePreferredLanguages": true, + "usePreferredReducedMotion": true, + "usePrevious": true, + "useRafFn": true, + "useRefHistory": true, + "useResizeObserver": true, + "useRoute": true, + "useRouter": true, + "useScreenOrientation": true, + "useScreenSafeArea": true, + "useScriptTag": true, + "useScroll": true, + "useScrollLock": true, + "useSessionStorage": true, + "useShare": true, + "useSlots": true, + "useSorted": true, + "useSpeechRecognition": true, + "useSpeechSynthesis": true, + "useStepper": true, + "useStorage": true, + "useStorageAsync": true, + "useStyleTag": true, + "useSupported": true, + "useSwipe": true, + "useTemplateRef": true, + "useTemplateRefsList": true, + "useTextDirection": true, + "useTextSelection": true, + "useTextareaAutosize": true, + "useThrottle": true, + "useThrottleFn": true, + "useThrottledRefHistory": true, + "useTimeAgo": true, + "useTimeout": true, + "useTimeoutFn": true, + "useTimeoutPoll": true, + "useTimestamp": true, + "useTitle": true, + "useToNumber": true, + "useToString": true, + "useToggle": true, + "useTransition": true, + "useUrlSearchParams": true, + "useUserMedia": true, + "useVModel": true, + "useVModels": true, + "useVibrate": true, + "useVirtualList": true, + "useWakeLock": true, + "useWebNotification": true, + "useWebSocket": true, + "useWebWorker": true, + "useWebWorkerFn": true, + "useWindowFocus": true, + "useWindowScroll": true, + "useWindowSize": true, + "watch": true, + "watchArray": true, + "watchAtMost": true, + "watchDebounced": true, + "watchDeep": true, + "watchEffect": true, + "watchIgnorable": true, + "watchImmediate": true, + "watchOnce": true, + "watchPausable": true, + "watchPostEffect": true, + "watchSyncEffect": true, + "watchThrottled": true, + "watchTriggerable": true, + "watchWithFilter": true, + "whenever": true + } +} diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..10a7ccb --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,41 @@ +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:vue/vue3-recommended' + ], + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 'latest', + parser: '@typescript-eslint/parser', + sourceType: 'module' + }, + plugins: ['@typescript-eslint', 'vue'], + rules: { + // Vue 规则 + 'vue/multi-word-component-names': 'off', + 'vue/no-v-html': 'off', + + // TypeScript 规则 + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['error', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_' + }], + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + + // 通用规则 + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'prefer-const': 'error', + 'no-var': 'error' + } +} diff --git a/.gitignore b/.gitignore index d183aae..7e7b15f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,55 @@ -# Secrets -.db_password -.redis_password +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* -# Runtime data -postgres/ -redis/ -backend/uploads/ -backend/logs/ +node_modules +dist +dist-ssr +*.local -# Python -__pycache__/ -*.pyc -*.pyo -*.pyd -.pytest_cache/ -.mypy_cache/ - -# Node -node_modules/ - -# OS +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea .DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage +.nyc_output/ + +# Documentation +*.md +docs/ +PHASE*.md +DELIVERY*.md +SUMMARY*.md +!README.md +!src/components/charts/README.md + +# Temporary files +*.tmp +*.temp +*.bak +*.backup + +# Lock files +package-lock.json +yarn.lock +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a94672b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "none", + "arrowParens": "avoid", + "endOfLine": "auto" +} diff --git a/CHARTS_FILES.txt b/CHARTS_FILES.txt new file mode 100644 index 0000000..103047d --- /dev/null +++ b/CHARTS_FILES.txt @@ -0,0 +1,64 @@ +图表组件库文件清单 +=================== + +核心组件 +-------- +src/components/charts/BaseChart.vue +src/components/charts/PieChart.vue +src/components/charts/BarChart.vue +src/components/charts/LineChart.vue +src/components/charts/GaugeChart.vue +src/components/charts/FunnelChart.vue + +业务图表组件 +------------ +src/components/charts/business/AssetStatusChart.vue +src/components/charts/business/AssetDistributionChart.vue +src/components/charts/business/AssetValueTrendChart.vue +src/components/charts/business/AssetUtilizationChart.vue + +统计卡片组件 +------------ +src/components/statistics/StatCard.vue +src/components/statistics/StatCardGroup.vue + +Composables +----------- +src/composables/useECharts.ts +src/composables/useChartData.ts + +工具函数 +-------- +src/utils/echarts.ts +src/utils/echarts/performance.ts + +类型定义 +-------- +src/types/charts.ts +src/components/charts/charts.d.ts + +组件导出 +-------- +src/components/charts/index.ts +src/components/charts/README.md +src/components/statistics/index.ts + +文档 +---- +CHARTS_README.md - 完整使用文档 +CHARTS_DELIVERY.md - 交付文档 +CHARTS_QUICKSTART.md - 快速开始指南 +CHARTS_SUMMARY.md - 项目总结 +CHARTS_FILES.txt - 本文件 + +示例和测试 +---------- +src/views/examples/ChartsExample.vue +tests/unit/components/PieChart.test.ts +tests/unit/composables/useECharts.test.ts + +路由配置 +-------- +src/router/index.ts (已添加示例页面路由) + +总计:25 个文件 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ded00ba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM nginx:alpine + +# 复制构建产物 +COPY dist /usr/share/nginx/html + +# 复制Nginx配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 暴露端口 +EXPOSE 3000 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..f82aad7 --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +# 资产管理系统前端 + +> 基于 Vue 3 + TypeScript + Element Plus 构建的现代化资产管理系统前端应用 + +## 技术栈 + +- **框架**: Vue 3.4+ (Composition API + ` + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..34f939b --- /dev/null +++ b/nginx.conf @@ -0,0 +1,23 @@ +server { + listen 3000; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json; + + # 处理前端路由 + location / { + try_files $uri $uri/ /index.html; + } + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b38b77b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6381 @@ +{ + "name": "asset-management-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "asset-management-frontend", + "version": "1.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@vueuse/core": "^10.7.2", + "axios": "^1.6.5", + "dayjs": "^1.11.10", + "echarts": "^5.4.3", + "element-plus": "^2.5.2", + "lodash-es": "^4.17.21", + "nprogress": "^0.2.0", + "pinia": "^2.1.7", + "qrcode": "^1.5.3", + "vee-validate": "^4.12.5", + "vue": "^3.4.15", + "vue-echarts": "^8.0.1", + "vue-router": "^4.2.5", + "yup": "^1.3.3" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.11.5", + "@types/nprogress": "^0.2.3", + "@types/qrcode": "^1.5.5", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "@vitejs/plugin-vue": "^5.0.3", + "@vue/test-utils": "^2.4.3", + "eslint": "^8.56.0", + "eslint-plugin-vue": "^9.19.2", + "prettier": "^3.2.4", + "sass": "^1.70.0", + "typescript": "^5.3.3", + "unplugin-auto-import": "^0.17.3", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.0.11", + "vitest": "^1.2.1", + "vue-tsc": "^1.8.27" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/nprogress": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.3.tgz", + "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@volar/language-core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "1.11.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", + "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.27", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", + "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", + "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.27", + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", + "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", + "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", + "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", + "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/runtime-core": "3.5.27", + "@vue/shared": "3.5.27", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", + "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "vue": "3.5.27" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", + "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", + "license": "MIT" + }, + "node_modules/@vue/test-utils": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^2.0.0" + } + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/element-plus": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.1.tgz", + "integrity": "sha512-eG4BDBGdAsUGN6URH1PixzZb0ngdapLivIk1meghS1uEueLvQ3aljSKrCt5x6sYb6mUk8eGtzTQFgsPmLavQcA==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^10.11.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz", + "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/unimport/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz", + "integrity": "sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.0", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.10", + "minimatch": "^9.0.4", + "unimport": "^3.7.2", + "unplugin": "^1.11.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-auto-import/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz", + "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.6", + "@rollup/pluginutils": "^5.0.4", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.3", + "minimatch": "^9.0.3", + "resolve": "^1.22.4", + "unplugin": "^1.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/unplugin-vue-components/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/unplugin-vue-components/node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unplugin-vue-components/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vee-validate": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/vee-validate/-/vee-validate-4.15.1.tgz", + "integrity": "sha512-DkFsiTwEKau8VIxyZBGdO6tOudD+QoUBPuHj3e6QFqmbfCRj1ArmYWue9lEp6jLSWBIw4XPlDLjFIZNLdRAMSg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.5.2", + "type-fest": "^4.8.3" + }, + "peerDependencies": { + "vue": "^3.4.26" + } + }, + "node_modules/vee-validate/node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/vee-validate/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", + "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-sfc": "3.5.27", + "@vue/runtime-dom": "3.5.27", + "@vue/server-renderer": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.12.tgz", + "integrity": "sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-echarts": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-8.0.1.tgz", + "integrity": "sha512-23rJTFLu1OUEGRWjJGmdGt8fP+8+ja1gVgzMYPIPaHWpXegcO1viIAaeu2H4QHESlVeHzUAHIxKXGrwjsyXAaA==", + "license": "MIT", + "peerDependencies": { + "echarts": "^6.0.0", + "vue": "^3.3.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/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/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yup": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", + "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", + "license": "MIT", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ceeb219 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "asset-management-frontend", + "version": "1.0.0", + "private": true, + "description": "资产管理系统前端", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", + "format": "prettier --write src/", + "test": "vitest" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@vueuse/core": "^10.7.2", + "axios": "^1.6.5", + "dayjs": "^1.11.10", + "echarts": "^5.4.3", + "element-plus": "^2.5.2", + "lodash-es": "^4.17.21", + "nprogress": "^0.2.0", + "pinia": "^2.1.7", + "qrcode": "^1.5.3", + "vee-validate": "^4.12.5", + "vue": "^3.4.15", + "vue-echarts": "^8.0.1", + "vue-router": "^4.2.5", + "yup": "^1.3.3" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.11.5", + "@types/nprogress": "^0.2.3", + "@types/qrcode": "^1.5.5", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "@vitejs/plugin-vue": "^5.0.3", + "@vue/test-utils": "^2.4.3", + "eslint": "^8.56.0", + "eslint-plugin-vue": "^9.19.2", + "prettier": "^3.2.4", + "sass": "^1.70.0", + "typescript": "^5.3.3", + "unplugin-auto-import": "^0.17.3", + "unplugin-vue-components": "^0.26.0", + "vite": "^5.0.11", + "vitest": "^1.2.1", + "vue-tsc": "^1.8.27" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..0cdddeb --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,107 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * Playwright配置文件 + * E2E测试配置 + */ +export default defineConfig({ + // 测试目录 + testDir: './tests/e2e', + + // 测试文件匹配模式 + testMatch: '**/*.spec.ts', + + // 完全并行运行测试文件 + fullyParallel: true, + + // 在CI中失败时不重试 + // 在本地开发中重试失败的测试 + retries: process.env.CI ? 0 : 2, + + // 在CI中限制并行工作线程数 + // 在本地使用所有可用线程 + workers: process.env.CI ? 2 : undefined, + + // 测试报告 + reporter: [ + ['html', { outputFolder: 'test_reports/playwright-report' }], + ['json', { outputFile: 'test_reports/playwright-results.json' }], + ['junit', { outputFile: 'test_reports/playwright-junit.xml' }], + ['list'] + ], + + // 全局设置 + use: { + // 基础URL + baseURL: 'http://localhost:5173', + + // 收集失败测试的追踪信息 + trace: 'retain-on-failure', + + // 截图配置 + screenshot: 'only-on-failure', + + // 视频配置 + video: 'retain-on-failure', + + // 操作超时时间 + actionTimeout: 10 * 1000, // 10秒 + navigationTimeout: 30 * 1000, // 30秒 + + // 浏览器视口大小 + viewport: { width: 1280, height: 720 }, + + // 忽略HTTPS错误 + ignoreHTTPSErrors: true, + + // 等待超时 + waitUntil: 'networkidle' + }, + + // 项目配置(不同浏览器) + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* 测试移动端视图 */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + ], + + // 开发服务器配置(启动测试前自动启动) + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, // 2分钟 + }, + + // 全局setup + globalSetup: './tests/e2e/global-setup.ts', + + // 全局teardown + globalTeardown: './tests/e2e/global-teardown.ts', + + // 期望超时 + expect: { + timeout: 5000 + } +}) diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..c439e3c --- /dev/null +++ b/src/App.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/src/api/assets.ts b/src/api/assets.ts new file mode 100644 index 0000000..b2352d4 --- /dev/null +++ b/src/api/assets.ts @@ -0,0 +1,102 @@ +/** + * 资产管理 API + */ +import { request } from './request' +import type { + Asset, + PaginationParams, + PaginationResponse, + DynamicField +} from '@/types' + +/** 资产列表参数 */ +export interface AssetListParams extends PaginationParams { + keyword?: string + device_type_id?: number + organization_id?: number + status?: string + purchase_date_start?: string + purchase_date_end?: string +} + +/** 创建资产参数 */ +export interface AssetCreateParams { + assetName: string + deviceTypeId: number + brandId?: number + model?: string + serialNumber?: string + supplierId?: number + purchaseDate?: string + purchasePrice?: number + warrantyPeriod?: number + organizationId: number + location?: string + dynamicAttributes: Record +} + +/** 更新资产参数 */ +export type AssetUpdateParams = Partial + +/** + * 获取资产列表 + */ +export const getAssetList = (params: AssetListParams) => { + return request.get>('/assets', { params }) +} + +/** + * 获取资产详情 + */ +export const getAssetById = (id: number) => { + return request.get(`/assets/${id}`) +} + +/** + * 根据编码查询资产 + */ +export const getAssetByCode = (code: string) => { + return request.get(`/assets/scan/${code}`) +} + +/** + * 创建资产 + */ +export const createAsset = (data: AssetCreateParams) => { + return request.post('/assets', data) +} + +/** + * 更新资产 + */ +export const updateAsset = (id: number, data: AssetUpdateParams) => { + return request.put(`/assets/${id}`, data) +} + +/** + * 删除资产 + */ +export const deleteAsset = (id: number) => { + return request.delete(`/assets/${id}`) +} + +/** + * 批量导入资产 + */ +export const importAssets = (file: File, onProgress?: (percent: number) => void) => { + return request.upload('/assets/import', file, onProgress) +} + +/** + * 批量导出资产 + */ +export const exportAssets = (params: AssetListParams) => { + return request.download('/assets/export', `资产列表_${Date.now()}.xlsx`) +} + +/** + * 获取设备类型的动态字段 + */ +export const getDeviceTypeFields = (typeId: number) => { + return request.get(`/device-types/${typeId}/fields`) +} diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..6c57bd5 --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,78 @@ +/** + * 认证相关 API + */ +import { request } from './request' +import type { UserInfo } from '@/types' + +/** 登录请求参数 */ +export interface LoginParams { + username: string + password: string + captcha: string + captcha_key: string +} + +/** 登录响应 */ +export interface LoginResponse { + access_token: string + refresh_token: string + token_type: string + expires_in: number + user: UserInfo +} + +/** 刷新 Token 参数 */ +export interface RefreshTokenParams { + refresh_token: string +} + +/** 刷新 Token 响应 */ +export interface RefreshTokenResponse { + access_token: string + expires_in: number +} + +/** 修改密码参数 */ +export interface ChangePasswordParams { + old_password: string + new_password: string + confirm_password: string +} + +/** + * 用户登录 + */ +export const login = (data: LoginParams) => { + return request.post('/auth/login', data) +} + +/** + * 刷新 Token + */ +export const refreshToken = (data: RefreshTokenParams) => { + return request.post('/auth/refresh', data) +} + +/** + * 用户登出 + */ +export const logout = () => { + return request.post('/auth/logout') +} + +/** + * 修改密码 + */ +export const changePassword = (data: ChangePasswordParams) => { + return request.put('/auth/change-password', data) +} + +/** + * 获取验证码 + */ +export const getCaptcha = () => { + return request.get<{ + captcha_key: string + captcha_image: string + }>('/auth/captcha') +} diff --git a/src/api/device-types.ts b/src/api/device-types.ts new file mode 100644 index 0000000..27b0fcf --- /dev/null +++ b/src/api/device-types.ts @@ -0,0 +1,103 @@ +/** + * 设备类型管理 API + */ +import { request } from './request' +import type { DeviceType, DynamicField, PaginationResponse } from '@/types' + +/** 设备类型列表参数 */ +export interface DeviceTypeListParams { + category?: string + status?: string +} + +/** 创建设备类型参数 */ +export interface DeviceTypeCreateParams { + typeCode: string + typeName: string + category: string + description?: string + icon?: string + sortOrder?: number +} + +/** 更新设备类型参数 */ +export type DeviceTypeUpdateParams = Partial + +/** 创建字段参数 */ +export interface FieldCreateParams { + fieldCode: string + fieldName: string + fieldType: DynamicField['fieldType'] + isRequired: boolean + placeholder?: string + options?: Array<{ label: string; value: any }> + validationRules?: Record + defaultValue?: any + sortOrder: number +} + +/** 更新字段参数 */ +export type FieldUpdateParams = Partial + +/** + * 获取设备类型列表 + */ +export const getDeviceTypeList = (params?: DeviceTypeListParams) => { + return request.get('/device-types', { params }) +} + +/** + * 获取设备类型详情 + */ +export const getDeviceTypeById = (id: number) => { + return request.get(`/device-types/${id}`) +} + +/** + * 创建设备类型 + */ +export const createDeviceType = (data: DeviceTypeCreateParams) => { + return request.post('/device-types', data) +} + +/** + * 更新设备类型 + */ +export const updateDeviceType = (id: number, data: DeviceTypeUpdateParams) => { + return request.put(`/device-types/${id}`, data) +} + +/** + * 删除设备类型 + */ +export const deleteDeviceType = (id: number) => { + return request.delete(`/device-types/${id}`) +} + +/** + * 获取设备类型的字段配置 + */ +export const getDeviceTypeFields = (typeId: number) => { + return request.get(`/device-types/${typeId}/fields`) +} + +/** + * 添加字段 + */ +export const addDeviceTypeField = (typeId: number, data: FieldCreateParams) => { + return request.post(`/device-types/${typeId}/fields`, data) +} + +/** + * 更新字段 + */ +export const updateDeviceTypeField = (typeId: number, fieldId: number, data: FieldUpdateParams) => { + return request.put(`/device-types/${typeId}/fields/${fieldId}`, data) +} + +/** + * 删除字段 + */ +export const deleteDeviceTypeField = (typeId: number, fieldId: number) => { + return request.delete(`/device-types/${typeId}/fields/${fieldId}`) +} diff --git a/src/api/file.ts b/src/api/file.ts new file mode 100644 index 0000000..26445e8 --- /dev/null +++ b/src/api/file.ts @@ -0,0 +1,204 @@ +/** + * 文件管理 API + */ +import { request } from './request' + +export interface FileItem { + id: number + file_name: string + original_name: string + file_path: string + file_size: number + file_type: string + file_ext: string + uploader_id: number + uploader_name?: string + upload_time: string + thumbnail_path?: string + share_code?: string + share_expire_time?: string + download_count: number + remark?: string + download_url?: string + preview_url?: string + share_url?: string +} + +export interface FileUploadResponse { + id: number + file_name: string + original_name: string + file_size: number + file_type: string + file_path: string + download_url: string + preview_url?: string + message: string +} + +export interface FileShareResponse { + share_code: string + share_url: string + expire_time: string +} + +export interface FileStatistics { + total_files: number + total_size: number + total_size_human: string + type_distribution: Record + upload_today: number + upload_this_week: number + upload_this_month: number + top_uploaders: Array<{ + uploader_id: number + count: number + }> +} + +export interface FileQueryParams { + keyword?: string + file_type?: string + uploader_id?: number + start_date?: string + end_date?: string + page?: number + page_size?: number +} + +/** + * 上传文件 + */ +export function uploadFile(file: File, data?: { remark?: string }) { + const formData = new FormData() + formData.append('file', file) + if (data?.remark) { + formData.append('remark', data.remark) + } + + return request.post('/files/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +/** + * 获取文件列表 + */ +export function getFileList(params?: FileQueryParams) { + return request.get('/files', { params }) +} + +/** + * 获取文件详情 + */ +export function getFileDetail(id: number) { + return request.get(`/files/${id}`) +} + +/** + * 下载文件 + */ +export function downloadFile(id: number) { + return request.get(`/files/${id}/download`, { + responseType: 'blob' + }) +} + +/** + * 预览文件 + */ +export function previewFile(id: number) { + return request.get(`/files/${id}/preview`, { + responseType: 'blob' + }) +} + +/** + * 更新文件信息 + */ +export function updateFile(id: number, data: { remark?: string }) { + return request.put(`/files/${id}`, data) +} + +/** + * 删除文件 + */ +export function deleteFile(id: number) { + return request.delete(`/files/${id}`) +} + +/** + * 批量删除文件 + */ +export function deleteFilesBatch(fileIds: number[]) { + return request.delete('/files/batch', { data: { file_ids: fileIds } }) +} + +/** + * 生成分享链接 + */ +export function createShareLink(id: number, expireDays: number = 7) { + return request.post(`/files/${id}/share`, { + expire_days: expireDays + }) +} + +/** + * 访问分享文件 + */ +export function accessSharedFile(shareCode: string) { + return request.get(`/files/share/${shareCode}`, { + responseType: 'blob' + }) +} + +/** + * 获取文件统计 + */ +export function getFileStatistics(uploaderId?: number) { + return request.get('/files/statistics', { + params: uploaderId ? { uploader_id: uploaderId } : undefined + }) +} + +/** + * 初始化分片上传 + */ +export function initChunkUpload(data: { + file_name: string + file_size: number + file_type: string + total_chunks: number + file_hash?: string +}) { + return request.post<{ upload_id: string; message: string }>('/files/chunks/init', data) +} + +/** + * 上传分片 + */ +export function uploadChunk(uploadId: string, chunkIndex: number, chunk: Blob) { + const formData = new FormData() + formData.append('upload_id', uploadId) + formData.append('chunk_index', chunkIndex.toString()) + formData.append('chunk', chunk) + + return request.post('/files/chunks/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +/** + * 完成分片上传 + */ +export function completeChunkUpload(data: { + upload_id: string + file_name: string + file_hash?: string +}) { + return request.post('/files/chunks/complete', data) +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..17e3de9 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,244 @@ +/** + * API 统一导出 + */ +export { request } from './request' +export * from './auth' +export * from './assets' +export * from './users' + +/** + * 设备类型管理 API + */ +export const getDeviceTypes = (params?: { category?: string; status?: string }) => { + return request.get('/device-types', { params }) +} + +export const createDeviceType = (data: any) => { + return request.post('/device-types', data) +} + +export const updateDeviceType = (id: number, data: any) => { + return request.put(`/device-types/${id}`, data) +} + +export const deleteDeviceType = (id: number) => { + return request.delete(`/device-types/${id}`) +} + +/** + * 角色权限 API + */ +export const getRoles = (params?: { status?: string }) => { + return request.get('/roles', { params }) +} + +export const createRole = (data: any) => { + return request.post('/roles', data) +} + +export const updateRole = (id: number, data: any) => { + return request.put(`/roles/${id}`, data) +} + +export const deleteRole = (id: number) => { + return request.delete(`/roles/${id}`) +} + +export const getPermissionTree = () => { + return request.get('/permissions/tree') +} + +/** + * 机构网点 API + */ +export const getOrganizationTree = () => { + return request.get('/organizations/tree') +} + +export const createOrganization = (data: any) => { + return request.post('/organizations', data) +} + +export const updateOrganization = (id: number, data: any) => { + return request.put(`/organizations/${id}`, data) +} + +export const deleteOrganization = (id: number) => { + return request.delete(`/organizations/${id}`) +} + +/** + * 资产分配 API + */ +export const getAllocationOrders = (params?: any) => { + return request.get('/allocation-orders', { params }) +} + +export const createAllocationOrder = (data: any) => { + return request.post('/allocation-orders', data) +} + +export const approveAllocationOrder = (id: number, data: any) => { + return request.post(`/allocation-orders/${id}/approve`, data) +} + +export const getAllocationOrderDetail = (id: number) => { + return request.get(`/allocation-orders/${id}`) +} + +/** + * 维修管理 API + */ +export const getMaintenanceRecords = (params?: any) => { + return request.get('/maintenance-records', { params }) +} + +export const createMaintenanceRecord = (data: any) => { + return request.post('/maintenance-records', data) +} + +export const updateMaintenanceRecord = (id: number, data: any) => { + return request.put(`/maintenance-records/${id}`, data) +} + +/** + * 统计分析 API + */ +export const getStatisticsOverview = () => { + return request.get('/statistics/overview') +} + +export const getStatisticsTrend = (params: { period: string }) => { + return request.get('/statistics/trend', { params }) +} + +/** + * 调拨管理 API + */ +export const getTransferList = (params?: any) => { + return request.get('/transfers', { params }) +} + +export const createTransfer = (data: any) => { + return request.post('/transfers', data) +} + +export const getTransferDetail = (id: number) => { + return request.get(`/transfers/${id}`) +} + +export const startTransfer = (id: number, data: any) => { + return request.post(`/transfers/${id}/start`, data) +} + +export const completeTransfer = (id: number) => { + return request.post(`/transfers/${id}/complete`) +} + +export const cancelTransfer = (id: number) => { + return request.post(`/transfers/${id}/cancel`) +} + +export const approveTransfer = (id: number, data: any) => { + return request.post(`/transfers/${id}/approve`, data) +} + +export const executeTransfer = (id: number) => { + return request.post(`/transfers/${id}/execute`) +} + +/** + * 回收管理 API + */ +export const getRecoveryList = (params?: any) => { + return request.get('/recoveries', { params }) +} + +export const createRecovery = (data: any) => { + return request.post('/recoveries', data) +} + +export const getRecoveryDetail = (id: number) => { + return request.get(`/recoveries/${id}`) +} + +export const startRecovery = (id: number, data: any) => { + return request.post(`/recoveries/${id}/start`, data) +} + +export const completeRecovery = (id: number) => { + return request.post(`/recoveries/${id}/complete`) +} + +export const cancelRecovery = (id: number) => { + return request.post(`/recoveries/${id}/cancel`) +} + +export const approveRecovery = (id: number, data: any) => { + return request.post(`/recoveries/${id}/approve`, data) +} + +export const executeRecovery = (id: number) => { + return request.post(`/recoveries/${id}/execute`) +} + +/** + * 系统配置 API + */ +export const getConfigList = () => { + return request.get('/system-config') +} + +export const createConfig = (data: any) => { + return request.post('/system-config', data) +} + +export const updateConfig = (id: number, data: any) => { + return request.put(`/system-config/${id}`, data) +} + +export const deleteConfig = (id: number) => { + return request.delete(`/system-config/${id}`) +} + +export const refreshConfigCache = () => { + return request.post('/system-config/refresh-cache') +} + +/** + * 操作日志 API + */ +export const getOperationLogs = (params?: any) => { + return request.get('/operation-logs', { params }) +} + +export const getOperationLogDetail = (id: number) => { + return request.get(`/operation-logs/${id}`) +} + +/** + * 消息通知 API + */ +export const getNotifications = (params?: any) => { + return request.get('/notifications', { params }) +} + +export const getNotificationDetail = (id: number) => { + return request.get(`/notifications/${id}`) +} + +export const markAsRead = (ids: number[]) => { + return request.put('/notifications/read', { ids }) +} + +export const markAsUnread = (ids: number[]) => { + return request.post('/notifications/mark-unread', { ids }) +} + +export const markAllAsRead = () => { + return request.post('/notifications/mark-all-read') +} + +export const deleteNotification = (ids: number[]) => { + return request.delete('/notifications', { data: { ids } }) +} diff --git a/src/api/organizations.ts b/src/api/organizations.ts new file mode 100644 index 0000000..34b83b9 --- /dev/null +++ b/src/api/organizations.ts @@ -0,0 +1,61 @@ +/** + * 机构网点管理 API + */ +import { request } from './request' +import type { Organization } from '@/types' + +/** 创建机构参数 */ +export interface OrganizationCreateParams { + orgCode: string + orgName: string + orgType: Organization['orgType'] + parentId?: number + address?: string + contactPerson?: string + contactPhone?: string +} + +/** 更新机构参数 */ +export type OrganizationUpdateParams = Partial> + +/** + * 获取机构树 + */ +export const getOrganizationTree = () => { + return request.get('/organizations/tree') +} + +/** + * 获取机构详情 + */ +export const getOrganizationById = (id: number) => { + return request.get(`/organizations/${id}`) +} + +/** + * 创建机构 + */ +export const createOrganization = (data: OrganizationCreateParams) => { + return request.post('/organizations', data) +} + +/** + * 更新机构 + */ +export const updateOrganization = (id: number, data: OrganizationUpdateParams) => { + return request.put(`/organizations/${id}`, data) +} + +/** + * 删除机构 + */ +export const deleteOrganization = (id: number) => { + return request.delete(`/organizations/${id}`) +} + +/** + * 移动机构(调整层级) + */ +export const moveOrganization = (id: number, targetParentId: number | null) => { + return request.put(`/organizations/${id}/move`, { target_parent_id: targetParentId }) +} diff --git a/src/api/request.ts b/src/api/request.ts new file mode 100644 index 0000000..fe4fd11 --- /dev/null +++ b/src/api/request.ts @@ -0,0 +1,196 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' +import { ElMessage } from 'element-plus' +import { useUserStore } from '@/stores/modules/user' +import type { ApiResponse } from '@/types' + +// 创建 axios 实例 +const service: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, + timeout: 15000, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + } +}) + +// 请求拦截器 +service.interceptors.request.use( + (config: AxiosRequestConfig) => { + const userStore = useUserStore() + + // 添加 Token + if (userStore.token) { + config.headers = config.headers || {} + config.headers.Authorization = `Bearer ${userStore.token}` + } + + // 添加时间戳防止缓存 + if (config.method === 'get') { + config.params = { + ...config.params, + _t: Date.now() + } + } + + return config + }, + (error: AxiosError) => { + console.error('Request error:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const res = response.data as ApiResponse + + // 如果响应的是文件流,直接返回 + if (response.config.responseType === 'blob') { + return response + } + + // 业务成功 + if (res.code === 200) { + return res.data + } + + // 业务失败 + // 特殊错误码处理 + if (res.code === 401) { + handleTokenExpired() + return Promise.reject(new Error('未授权')) + } + + // 其他错误才显示提示 + ElMessage.error(res.message || '请求失败') + return Promise.reject(new Error(res.message || '请求失败')) + }, + (error: AxiosError) => { + console.error('Response error:', error) + + if (error.response) { + const { status } = error.response + + switch (status) { + case 400: + ElMessage.error('请求参数错误') + break + case 401: + // 401错误不显示提示,直接跳转登录 + handleTokenExpired() + break + case 403: + ElMessage.error('拒绝访问') + break + case 404: + ElMessage.error('请求资源不存在') + break + case 405: + ElMessage.error('请求方法不允许') + break + case 408: + ElMessage.error('请求超时') + break + case 500: + ElMessage.error('服务器内部错误') + break + case 501: + ElMessage.error('服务未实现') + break + case 502: + ElMessage.error('网关错误') + break + case 503: + ElMessage.error('服务不可用') + break + case 504: + ElMessage.error('网关超时') + break + case 505: + ElMessage.error('HTTP版本不受支持') + break + default: + ElMessage.error(`连接错误${status}`) + } + } else if (error.request) { + ElMessage.error('网络连接失败,请检查网络') + } else { + ElMessage.error(error.message || '请求失败') + } + + return Promise.reject(error) + } +) + +/** + * 处理 Token 过期 + * 直接清理Token并跳转到登录页,不显示任何提示 + */ +function handleTokenExpired() { + const userStore = useUserStore() + + // 清理Token和用户信息 + userStore.logout() + + // 直接跳转到登录页,不显示任何提示 + window.location.href = '/login' +} + +/** + * 通用请求方法 + */ +export const request = { + get(url: string, config?: AxiosRequestConfig): Promise { + return service.get(url, config) + }, + + post(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return service.post(url, data, config) + }, + + put(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return service.put(url, data, config) + }, + + delete(url: string, config?: AxiosRequestConfig): Promise { + return service.delete(url, config) + }, + + patch(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return service.patch(url, data, config) + }, + + // 文件上传 + upload(url: string, file: File, onProgress?: (percent: number) => void): Promise { + const formData = new FormData() + formData.append('file', file) + + return service.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + }, + onUploadProgress: (progressEvent) => { + if (onProgress && progressEvent.total) { + const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total) + onProgress(percent) + } + } + }) + }, + + // 文件下载 + download(url: string, filename?: string): Promise { + return service.get(url, { + responseType: 'blob' + }).then((response: AxiosResponse) => { + const blob = new Blob([response.data]) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.download = filename || 'download' + link.click() + URL.revokeObjectURL(link.href) + }) + } +} + +export default service diff --git a/src/api/roles.ts b/src/api/roles.ts new file mode 100644 index 0000000..1d7362f --- /dev/null +++ b/src/api/roles.ts @@ -0,0 +1,65 @@ +/** + * 角色权限 API + */ +import { request } from './request' +import type { Role, Permission, PaginationResponse } from '@/types' + +/** 角色列表参数 */ +export interface RoleListParams { + status?: string +} + +/** 创建角色参数 */ +export interface RoleCreateParams { + roleCode: string + roleName: string + description?: string + permissionIds: number[] +} + +/** 更新角色参数 */ +export type RoleUpdateParams = Partial> & { + permissionIds?: number[] +} + +/** + * 获取角色列表 + */ +export const getRoleList = (params?: RoleListParams) => { + return request.get('/roles', { params }) +} + +/** + * 获取角色详情 + */ +export const getRoleById = (id: number) => { + return request.get(`/roles/${id}`) +} + +/** + * 创建角色 + */ +export const createRole = (data: RoleCreateParams) => { + return request.post('/roles', data) +} + +/** + * 更新角色 + */ +export const updateRole = (id: number, data: RoleUpdateParams) => { + return request.put(`/roles/${id}`, data) +} + +/** + * 删除角色 + */ +export const deleteRole = (id: number) => { + return request.delete(`/roles/${id}`) +} + +/** + * 获取权限树 + */ +export const getPermissionTree = () => { + return request.get('/permissions/tree') +} diff --git a/src/api/users.ts b/src/api/users.ts new file mode 100644 index 0000000..1d945f0 --- /dev/null +++ b/src/api/users.ts @@ -0,0 +1,80 @@ +/** + * 用户管理 API + */ +import { request } from './request' +import type { + UserInfo, + PaginationParams, + PaginationResponse +} from '@/types' + +/** 用户列表参数 */ +export interface UserListParams extends PaginationParams { + keyword?: string + status?: string + role_id?: number +} + +/** 创建用户参数 */ +export interface UserCreateParams { + username: string + password: string + realName: string + email?: string + phone?: string + roleIds: number[] +} + +/** 更新用户参数 */ +export type UserUpdateParams = Partial> & { + status?: string +} + +/** + * 获取用户列表 + */ +export const getUserList = (params: UserListParams) => { + return request.get>('/users', { params }) +} + +/** + * 获取用户详情 + */ +export const getUserById = (id: number) => { + return request.get(`/users/${id}`) +} + +/** + * 获取当前用户信息 + */ +export const getCurrentUser = () => { + return request.get('/users/me') +} + +/** + * 创建用户 + */ +export const createUser = (data: UserCreateParams) => { + return request.post('/users', data) +} + +/** + * 更新用户 + */ +export const updateUser = (id: number, data: UserUpdateParams) => { + return request.put(`/users/${id}`, data) +} + +/** + * 删除用户 + */ +export const deleteUser = (id: number) => { + return request.delete(`/users/${id}`) +} + +/** + * 重置用户密码 + */ +export const resetUserPassword = (id: number, newPassword: string) => { + return request.post(`/users/${id}/reset-password`, { new_password: newPassword }) +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss new file mode 100644 index 0000000..a6815de --- /dev/null +++ b/src/assets/styles/index.scss @@ -0,0 +1,256 @@ +/** + * 全局样式 + */ +@import './variables.scss'; + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + width: 100%; + height: 100%; + font-family: $font-family; + font-size: $font-size-base; + color: $text-primary; + background-color: $bg-color; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#app { + width: 100%; + height: 100%; +} + +a { + color: $primary-color; + text-decoration: none; + transition: $transition-base; + + &:hover { + color: darken($primary-color, 10%); + } +} + +// 滚动条样式 +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + background-color: $text-placeholder; + border-radius: $border-radius-base; + + &:hover { + background-color: $text-secondary; + } +} + +::-webkit-scrollbar-track { + background-color: $border-light; +} + +// 卡片样式 +.el-card { + border-radius: $border-radius-large; + box-shadow: $box-shadow-base; + border: 1px solid $border-base; + + .el-card__header { + border-bottom: 1px solid $border-base; + padding: $spacing-medium $spacing-large; + font-weight: 600; + } + + .el-card__body { + padding: $spacing-large; + } +} + +// 表格样式 +.el-table { + .el-table__header-wrapper { + th { + background-color: $bg-color-page; + color: $text-regular; + font-weight: 600; + } + } + + .el-table__body-wrapper { + .el-table__row { + &:hover > td { + background-color: $border-lighter; + } + } + } +} + +// 按钮样式 +.el-button { + &.el-button--primary { + background-color: $primary-color; + border-color: $primary-color; + + &:hover { + background-color: darken($primary-color, 10%); + border-color: darken($primary-color, 10%); + } + } +} + +// 对话框样式 +.el-dialog { + border-radius: $border-radius-large; + + .el-dialog__header { + border-bottom: 1px solid $border-base; + padding: $spacing-medium $spacing-large; + } + + .el-dialog__body { + padding: $spacing-large; + } + + .el-dialog__footer { + border-top: 1px solid $border-base; + padding: $spacing-medium $spacing-large; + } +} + +// 表单样式 +.el-form { + .el-form-item__label { + color: $text-regular; + font-weight: 500; + } + + .el-input__inner, + .el-textarea__inner { + border-color: $border-base; + + &:focus { + border-color: $primary-color; + } + } +} + +// 消息提示样式 +.el-message { + border-radius: $border-radius-base; + box-shadow: $box-shadow-dark; +} + +// 标签样式 +.el-tag { + border-radius: $border-radius-small; +} + +// 分页样式 +.el-pagination { + .el-pager li { + &.active { + background-color: $primary-color; + } + } + + .btn-prev, + .btn-next { + &:disabled { + background-color: $bg-color-page; + } + } +} + +// 工具类 +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.mt-10 { + margin-top: 10px; +} + +.mt-20 { + margin-top: 20px; +} + +.mb-10 { + margin-bottom: 10px; +} + +.mb-20 { + margin-bottom: 20px; +} + +.ml-10 { + margin-left: 10px; +} + +.mr-10 { + margin-right: 10px; +} + +.p-10 { + padding: 10px; +} + +.p-20 { + padding: 20px; +} + +.flex { + display: flex; +} + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +.flex-1 { + flex: 1; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +// 加载动画 +@keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.loading { + display: inline-block; + animation: rotate 1s linear infinite; +} diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss new file mode 100644 index 0000000..a49b1ec --- /dev/null +++ b/src/assets/styles/variables.scss @@ -0,0 +1,86 @@ +/** + * 主题变量 - 青灰配色方案 + */ + +// 主题色 +$primary-color: #475569; +$success-color: #10b981; +$warning-color: #f59e0b; +$danger-color: #ef4444; +$info-color: #3b82f6; + +// 中性色 +$text-primary: #1e293b; +$text-regular: #475569; +$text-secondary: #64748b; +$text-placeholder: #94a3b8; +$text-disabled: #cbd5e1; + +// 边框色 +$border-base: #e2e8f0; +$border-light: #f1f5f9; +$border-lighter: #f8fafc; +$border-extra-light: #f8fafc; + +// 背景色 +$bg-color: #f8fafc; +$bg-color-page: #f1f5f9; +$bg-color-overlay: #ffffff; + +// 字体 +$font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; +$font-size-base: 14px; +$font-size-small: 12px; +$font-size-medium: 14px; +$font-size-large: 16px; +$font-size-extra-large: 20px; + +// 圆角 +$border-radius-small: 2px; +$border-radius-base: 4px; +$border-radius-large: 8px; +$border-radius-circle: 50%; + +// 阴影 +$box-shadow-base: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +$box-shadow-dark: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +$box-shadow-light: 0 0 2px 0 rgba(0, 0, 0, 0.05); + +// 间距 +$spacing-small: 4px; +$spacing-base: 8px; +$spacing-medium: 16px; +$spacing-large: 24px; +$spacing-extra-large: 32px; + +// 高度 +$height-small: 24px; +$height-base: 32px; +$height-medium: 36px; +$height-large: 40px; +$height-extra-large: 48px; + +// 侧边栏 +$sidebar-width: 200px; +$sidebar-collapsed-width: 64px; +$sidebar-bg: #1e293b; +$sidebar-text-color: #94a3b8; +$sidebar-active-bg: rgba(71, 85, 105, 0.2); +$sidebar-active-text: #ffffff; + +// 头部 +$header-height: 60px; +$header-bg: #ffffff; +$header-border: #e2e8f0; + +// 过渡 +$transition-base: all 0.3s ease; +$transition-fade: opacity 0.3s ease; +$transition-slide: transform 0.3s ease; + +// z-index +$z-index-normal: 1; +$z-index-top: 1000; +$z-index-popper: 2000; diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts new file mode 100644 index 0000000..541c8be --- /dev/null +++ b/src/auto-imports.d.ts @@ -0,0 +1,308 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] + const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] + const computed: typeof import('vue')['computed'] + const computedAsync: typeof import('@vueuse/core')['computedAsync'] + const computedEager: typeof import('@vueuse/core')['computedEager'] + const computedInject: typeof import('@vueuse/core')['computedInject'] + const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] + const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] + const controlledRef: typeof import('@vueuse/core')['controlledRef'] + const createApp: typeof import('vue')['createApp'] + const createEventHook: typeof import('@vueuse/core')['createEventHook'] + const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] + const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] + const createPinia: typeof import('pinia')['createPinia'] + const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] + const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] + const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] + const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] + const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] + const customRef: typeof import('vue')['customRef'] + const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] + const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] + const effectScope: typeof import('vue')['effectScope'] + const extendRef: typeof import('@vueuse/core')['extendRef'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] + const inject: typeof import('vue')['inject'] + const injectLocal: typeof import('@vueuse/core')['injectLocal'] + const isDefined: typeof import('@vueuse/core')['isDefined'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] + const onLongPress: typeof import('@vueuse/core')['onLongPress'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] + const provide: typeof import('vue')['provide'] + const provideLocal: typeof import('@vueuse/core')['provideLocal'] + const reactify: typeof import('@vueuse/core')['reactify'] + const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] + const reactive: typeof import('vue')['reactive'] + const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] + const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] + const reactivePick: typeof import('@vueuse/core')['reactivePick'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] + const refDebounced: typeof import('@vueuse/core')['refDebounced'] + const refDefault: typeof import('@vueuse/core')['refDefault'] + const refThrottled: typeof import('@vueuse/core')['refThrottled'] + const refWithControl: typeof import('@vueuse/core')['refWithControl'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const resolveRef: typeof import('@vueuse/core')['resolveRef'] + const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const syncRef: typeof import('@vueuse/core')['syncRef'] + const syncRefs: typeof import('@vueuse/core')['syncRefs'] + const templateRef: typeof import('@vueuse/core')['templateRef'] + const throttledRef: typeof import('@vueuse/core')['throttledRef'] + const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] + const toRaw: typeof import('vue')['toRaw'] + const toReactive: typeof import('@vueuse/core')['toReactive'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] + const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] + const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] + const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] + const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] + const unref: typeof import('vue')['unref'] + const unrefElement: typeof import('@vueuse/core')['unrefElement'] + const until: typeof import('@vueuse/core')['until'] + const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] + const useAnimate: typeof import('@vueuse/core')['useAnimate'] + const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] + const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] + const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] + const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] + const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] + const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] + const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes'] + const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] + const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] + const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] + const useArraySome: typeof import('@vueuse/core')['useArraySome'] + const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique'] + const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] + const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] + const useAttrs: typeof import('vue')['useAttrs'] + const useBase64: typeof import('@vueuse/core')['useBase64'] + const useBattery: typeof import('@vueuse/core')['useBattery'] + const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] + const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] + const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] + const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] + const useCached: typeof import('@vueuse/core')['useCached'] + const useClipboard: typeof import('@vueuse/core')['useClipboard'] + const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] + const useCloned: typeof import('@vueuse/core')['useCloned'] + const useColorMode: typeof import('@vueuse/core')['useColorMode'] + const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] + const useCounter: typeof import('@vueuse/core')['useCounter'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVar: typeof import('@vueuse/core')['useCssVar'] + const useCssVars: typeof import('vue')['useCssVars'] + const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] + const useCycleList: typeof import('@vueuse/core')['useCycleList'] + const useDark: typeof import('@vueuse/core')['useDark'] + const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] + const useDebounce: typeof import('@vueuse/core')['useDebounce'] + const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] + const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] + const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] + const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] + const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] + const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] + const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] + const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] + const useDraggable: typeof import('@vueuse/core')['useDraggable'] + const useDropZone: typeof import('@vueuse/core')['useDropZone'] + const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] + const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] + const useElementHover: typeof import('@vueuse/core')['useElementHover'] + const useElementSize: typeof import('@vueuse/core')['useElementSize'] + const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] + const useEventBus: typeof import('@vueuse/core')['useEventBus'] + const useEventListener: typeof import('@vueuse/core')['useEventListener'] + const useEventSource: typeof import('@vueuse/core')['useEventSource'] + const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] + const useFavicon: typeof import('@vueuse/core')['useFavicon'] + const useFetch: typeof import('@vueuse/core')['useFetch'] + const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] + const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] + const useFocus: typeof import('@vueuse/core')['useFocus'] + const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] + const useFps: typeof import('@vueuse/core')['useFps'] + const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] + const useGamepad: typeof import('@vueuse/core')['useGamepad'] + const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] + const useId: typeof import('vue')['useId'] + const useIdle: typeof import('@vueuse/core')['useIdle'] + const useImage: typeof import('@vueuse/core')['useImage'] + const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] + const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] + const useInterval: typeof import('@vueuse/core')['useInterval'] + const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] + const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] + const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] + const useLink: typeof import('vue-router')['useLink'] + const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] + const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] + const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] + const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] + const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] + const useMemoize: typeof import('@vueuse/core')['useMemoize'] + const useMemory: typeof import('@vueuse/core')['useMemory'] + const useModel: typeof import('vue')['useModel'] + const useMounted: typeof import('@vueuse/core')['useMounted'] + const useMouse: typeof import('@vueuse/core')['useMouse'] + const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] + const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] + const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] + const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] + const useNetwork: typeof import('@vueuse/core')['useNetwork'] + const useNow: typeof import('@vueuse/core')['useNow'] + const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] + const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] + const useOnline: typeof import('@vueuse/core')['useOnline'] + const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] + const useParallax: typeof import('@vueuse/core')['useParallax'] + const useParentElement: typeof import('@vueuse/core')['useParentElement'] + const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] + const usePermission: typeof import('@vueuse/core')['usePermission'] + const usePointer: typeof import('@vueuse/core')['usePointer'] + const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] + const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] + const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] + const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] + const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] + const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] + const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] + const usePrevious: typeof import('@vueuse/core')['usePrevious'] + const useRafFn: typeof import('@vueuse/core')['useRafFn'] + const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] + const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] + const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] + const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] + const useScroll: typeof import('@vueuse/core')['useScroll'] + const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] + const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] + const useShare: typeof import('@vueuse/core')['useShare'] + const useSlots: typeof import('vue')['useSlots'] + const useSorted: typeof import('@vueuse/core')['useSorted'] + const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] + const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] + const useStepper: typeof import('@vueuse/core')['useStepper'] + const useStorage: typeof import('@vueuse/core')['useStorage'] + const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] + const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] + const useSupported: typeof import('@vueuse/core')['useSupported'] + const useSwipe: typeof import('@vueuse/core')['useSwipe'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] + const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] + const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] + const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] + const useThrottle: typeof import('@vueuse/core')['useThrottle'] + const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] + const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] + const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] + const useTimeout: typeof import('@vueuse/core')['useTimeout'] + const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] + const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] + const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] + const useTitle: typeof import('@vueuse/core')['useTitle'] + const useToNumber: typeof import('@vueuse/core')['useToNumber'] + const useToString: typeof import('@vueuse/core')['useToString'] + const useToggle: typeof import('@vueuse/core')['useToggle'] + const useTransition: typeof import('@vueuse/core')['useTransition'] + const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] + const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] + const useVModel: typeof import('@vueuse/core')['useVModel'] + const useVModels: typeof import('@vueuse/core')['useVModels'] + const useVibrate: typeof import('@vueuse/core')['useVibrate'] + const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] + const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] + const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] + const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] + const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] + const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] + const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] + const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] + const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] + const watch: typeof import('vue')['watch'] + const watchArray: typeof import('@vueuse/core')['watchArray'] + const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] + const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] + const watchDeep: typeof import('@vueuse/core')['watchDeep'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] + const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] + const watchOnce: typeof import('@vueuse/core')['watchOnce'] + const watchPausable: typeof import('@vueuse/core')['watchPausable'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] + const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] + const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] + const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] + const whenever: typeof import('@vueuse/core')['whenever'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/src/components.d.ts b/src/components.d.ts new file mode 100644 index 0000000..4b3fc4c --- /dev/null +++ b/src/components.d.ts @@ -0,0 +1,99 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + AssetDistributionChart: typeof import('./components/charts/business/AssetDistributionChart.vue')['default'] + AssetStatusChart: typeof import('./components/charts/business/AssetStatusChart.vue')['default'] + AssetUtilizationChart: typeof import('./components/charts/business/AssetUtilizationChart.vue')['default'] + AssetValueTrendChart: typeof import('./components/charts/business/AssetValueTrendChart.vue')['default'] + BarChart: typeof import('./components/charts/BarChart.vue')['default'] + BaseChart: typeof import('./components/charts/BaseChart.vue')['default'] + BooleanField: typeof import('./components/form/fields/BooleanField.vue')['default'] + DateField: typeof import('./components/form/fields/DateField.vue')['default'] + DynamicFieldRenderer: typeof import('./components/form/DynamicFieldRenderer.vue')['default'] + ElAlert: typeof import('element-plus/es')['ElAlert'] + ElAside: typeof import('element-plus/es')['ElAside'] + ElAvatar: typeof import('element-plus/es')['ElAvatar'] + ElBadge: typeof import('element-plus/es')['ElBadge'] + ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] + ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] + ElCol: typeof import('element-plus/es')['ElCol'] + ElContainer: typeof import('element-plus/es')['ElContainer'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] + ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] + ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElDivider: typeof import('element-plus/es')['ElDivider'] + ElDropdown: typeof import('element-plus/es')['ElDropdown'] + ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] + ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElEmpty: typeof import('element-plus/es')['ElEmpty'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElHeader: typeof import('element-plus/es')['ElHeader'] + ElIcon: typeof import('element-plus/es')['ElIcon'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElMain: typeof import('element-plus/es')['ElMain'] + ElMenu: typeof import('element-plus/es')['ElMenu'] + ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPageHeader: typeof import('element-plus/es')['ElPageHeader'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm'] + ElProgress: typeof import('element-plus/es')['ElProgress'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElResult: typeof import('element-plus/es')['ElResult'] + ElRow: typeof import('element-plus/es')['ElRow'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] + ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem'] + ElSpace: typeof import('element-plus/es')['ElSpace'] + ElStep: typeof import('element-plus/es')['ElStep'] + ElSteps: typeof import('element-plus/es')['ElSteps'] + ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] + ElTree: typeof import('element-plus/es')['ElTree'] + ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] + ElUpload: typeof import('element-plus/es')['ElUpload'] + FieldDesigner: typeof import('./components/form/FieldDesigner.vue')['default'] + FileList: typeof import('./components/file/FileList.vue')['default'] + FileUpload: typeof import('./components/file/FileUpload.vue')['default'] + FunnelChart: typeof import('./components/charts/FunnelChart.vue')['default'] + GaugeChart: typeof import('./components/charts/GaugeChart.vue')['default'] + ImagePreview: typeof import('./components/file/ImagePreview.vue')['default'] + LineChart: typeof import('./components/charts/LineChart.vue')['default'] + MultiSelectField: typeof import('./components/form/fields/MultiSelectField.vue')['default'] + NotificationBell: typeof import('./components/NotificationBell.vue')['default'] + NumberField: typeof import('./components/form/fields/NumberField.vue')['default'] + PieChart: typeof import('./components/charts/PieChart.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + SelectField: typeof import('./components/form/fields/SelectField.vue')['default'] + StatCard: typeof import('./components/statistics/StatCard.vue')['default'] + StatCardGroup: typeof import('./components/statistics/StatCardGroup.vue')['default'] + TextareaField: typeof import('./components/form/fields/TextareaField.vue')['default'] + TextField: typeof import('./components/form/fields/TextField.vue')['default'] + TreeSelect: typeof import('./components/common/TreeSelect.vue')['default'] + } + export interface ComponentCustomProperties { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } +} diff --git a/src/components/NotificationBell.vue b/src/components/NotificationBell.vue new file mode 100644 index 0000000..133593f --- /dev/null +++ b/src/components/NotificationBell.vue @@ -0,0 +1,336 @@ + + + + + diff --git a/src/components/charts/BarChart.vue b/src/components/charts/BarChart.vue new file mode 100644 index 0000000..d7e784c --- /dev/null +++ b/src/components/charts/BarChart.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/src/components/charts/BaseChart.vue b/src/components/charts/BaseChart.vue new file mode 100644 index 0000000..d69186f --- /dev/null +++ b/src/components/charts/BaseChart.vue @@ -0,0 +1,114 @@ + + + + + + + diff --git a/src/components/charts/FunnelChart.vue b/src/components/charts/FunnelChart.vue new file mode 100644 index 0000000..bfe9cc5 --- /dev/null +++ b/src/components/charts/FunnelChart.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/charts/GaugeChart.vue b/src/components/charts/GaugeChart.vue new file mode 100644 index 0000000..15901e3 --- /dev/null +++ b/src/components/charts/GaugeChart.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/components/charts/LineChart.vue b/src/components/charts/LineChart.vue new file mode 100644 index 0000000..ace3629 --- /dev/null +++ b/src/components/charts/LineChart.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/src/components/charts/PieChart.vue b/src/components/charts/PieChart.vue new file mode 100644 index 0000000..f000127 --- /dev/null +++ b/src/components/charts/PieChart.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/src/components/charts/README.md b/src/components/charts/README.md new file mode 100644 index 0000000..c139531 --- /dev/null +++ b/src/components/charts/README.md @@ -0,0 +1,105 @@ +/** + * 图表组件库完整导出文件 + * + * 统一导出所有图表组件、类型、工具函数等 + */ + +// ==================== 通用图表组件 ==================== +export { default as BaseChart } from './BaseChart.vue' +export { default as PieChart } from './PieChart.vue' +export { default as BarChart } from './BarChart.vue' +export { default as LineChart } from './LineChart.vue' +export { default as GaugeChart } from './GaugeChart.vue' +export { default as FunnelChart } from './FunnelChart.vue' + +// ==================== 业务图表组件 ==================== +export { default as AssetStatusChart } from './business/AssetStatusChart.vue' +export { default as AssetDistributionChart } from './business/AssetDistributionChart.vue' +export { default AssetValueTrendChart } from './business/AssetValueTrendChart.vue' +export { default as AssetUtilizationChart } from './business/AssetUtilizationChart.vue' + +// ==================== 统计卡片组件 ==================== +export { default as StatCard } from '../statistics/StatCard.vue' +export { default as StatCardGroup } from '../statistics/StatCardGroup.vue' + +// ==================== Composables ==================== +export { useECharts } from '@/composables/useECharts' +export { useChartData } from '@/composables/useChartData' + +// ==================== 工具函数 ==================== +export { + // 主题配置 + echartsTheme, + assetStatusColors, + assetStatusNames, + + // 图表配置 + baseChartOption, + pieChartOption, + barChartOption, + lineChartOption, + gaugeChartOption, + funnelChartOption, + + // 格式化函数 + formatNumber, + formatCurrency, + formatPercentage, + getColor, + getAssetStatusColor, + getAssetStatusName, + + // 工具函数 + resizeChart, + mergeOption, +} from '@/utils/echarts' + +// ==================== 性能优化 ==================== +export { + performanceConfig, + applyPerformanceConfig, + sampleData, + aggregateDataByTime, + paginateData, + lttbDownsampling, + debounce, + throttle, + createPerformanceMonitor, + type ChartPerformanceMonitor, +} from '@/utils/echarts/performance' + +// ==================== 类型定义 ==================== +export type { + // 基础类型 + ChartDataItem, + ChartSeries, + + // 配置类型 + PieChartConfig, + BarChartConfig, + LineChartConfig, + GaugeChartConfig, + FunnelChartConfig, + StatCardConfig, + + // 业务类型 + AssetStatusStatistics, + AssetDistributionStatistics, + AssetTrendData, + AssetTypeStatistics, + MaintenanceStatistics, + + // 其他类型 + ChartTheme, + ChartSize, + ChartEvents, + ChartExportConfig, + ChartResponsiveConfig, + ChartLoadingConfig, + ChartAnimationConfig, + ChartPerformanceConfig, +} from '@/types/charts' + +// ==================== 常量 ==================== +export const CHART_VERSION = '1.0.0' +export const CHART_AUTHOR = '图表组件开发组' diff --git a/src/components/charts/business/AssetDistributionChart.vue b/src/components/charts/business/AssetDistributionChart.vue new file mode 100644 index 0000000..92a10f6 --- /dev/null +++ b/src/components/charts/business/AssetDistributionChart.vue @@ -0,0 +1,77 @@ + + + + + + + diff --git a/src/components/charts/business/AssetStatusChart.vue b/src/components/charts/business/AssetStatusChart.vue new file mode 100644 index 0000000..5718612 --- /dev/null +++ b/src/components/charts/business/AssetStatusChart.vue @@ -0,0 +1,70 @@ + + + + + + + diff --git a/src/components/charts/business/AssetUtilizationChart.vue b/src/components/charts/business/AssetUtilizationChart.vue new file mode 100644 index 0000000..a6fd587 --- /dev/null +++ b/src/components/charts/business/AssetUtilizationChart.vue @@ -0,0 +1,62 @@ + + + + + + + diff --git a/src/components/charts/business/AssetValueTrendChart.vue b/src/components/charts/business/AssetValueTrendChart.vue new file mode 100644 index 0000000..f1ab741 --- /dev/null +++ b/src/components/charts/business/AssetValueTrendChart.vue @@ -0,0 +1,93 @@ + + + + + + + diff --git a/src/components/charts/charts.d.ts b/src/components/charts/charts.d.ts new file mode 100644 index 0000000..f9ed5ec --- /dev/null +++ b/src/components/charts/charts.d.ts @@ -0,0 +1,23 @@ +/** + * 图表组件 TypeScript 声明 + * 提供更好的类型提示 + */ + +import type { DefineComponent } from 'vue' + +/** BaseChart 组件 */ +export interface BaseChartProps { + option: any + height?: string + autoResize?: boolean + loading?: boolean + theme?: string | object +} + +export interface BaseChartEmits { + ready: (chart: any) => void + click: (params: any) => void +} + +declare const BaseChart: DefineComponent +export default BaseChart diff --git a/src/components/charts/index.ts b/src/components/charts/index.ts new file mode 100644 index 0000000..71b92ae --- /dev/null +++ b/src/components/charts/index.ts @@ -0,0 +1,21 @@ +/** + * 图表组件统一导出 + */ + +// 统计卡片组件 +export { default as StatCard } from '../statistics/StatCard.vue' +export { default as StatCardGroup } from '../statistics/StatCardGroup.vue' + +// 通用图表组件 +export { default as BaseChart } from './BaseChart.vue' +export { default as PieChart } from './PieChart.vue' +export { default as BarChart } from './BarChart.vue' +export { default as LineChart } from './LineChart.vue' +export { default as GaugeChart } from './GaugeChart.vue' +export { default as FunnelChart } from './FunnelChart.vue' + +// 业务图表组件 +export { default as AssetStatusChart } from './business/AssetStatusChart.vue' +export { default as AssetDistributionChart } from './business/AssetDistributionChart.vue' +export { default as AssetValueTrendChart } from './business/AssetValueTrendChart.vue' +export { default as AssetUtilizationChart } from './business/AssetUtilizationChart.vue' diff --git a/src/components/common/TreeSelect.vue b/src/components/common/TreeSelect.vue new file mode 100644 index 0000000..e8c008f --- /dev/null +++ b/src/components/common/TreeSelect.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/components/file/FileList.vue b/src/components/file/FileList.vue new file mode 100644 index 0000000..81356ff --- /dev/null +++ b/src/components/file/FileList.vue @@ -0,0 +1,601 @@ + + + + + diff --git a/src/components/file/FileUpload.vue b/src/components/file/FileUpload.vue new file mode 100644 index 0000000..bd97b6d --- /dev/null +++ b/src/components/file/FileUpload.vue @@ -0,0 +1,438 @@ + + + + + diff --git a/src/components/file/ImagePreview.vue b/src/components/file/ImagePreview.vue new file mode 100644 index 0000000..ece945e --- /dev/null +++ b/src/components/file/ImagePreview.vue @@ -0,0 +1,400 @@ + + + + + diff --git a/src/components/file/index.ts b/src/components/file/index.ts new file mode 100644 index 0000000..d246f61 --- /dev/null +++ b/src/components/file/index.ts @@ -0,0 +1,19 @@ +/** + * 文件管理组件入口 + */ + +import FileUpload from './FileUpload.vue' +import FileList from './FileList.vue' +import ImagePreview from './ImagePreview.vue' + +export { + FileUpload, + FileList, + ImagePreview +} + +export default { + FileUpload, + FileList, + ImagePreview +} diff --git a/src/components/file/types.ts b/src/components/file/types.ts new file mode 100644 index 0000000..02653a6 --- /dev/null +++ b/src/components/file/types.ts @@ -0,0 +1,322 @@ +/** + * 文件管理组件类型定义 + */ + +import { Ref } from 'vue' + +/** + * 文件项 + */ +export interface FileItem { + id: number + file_name: string + original_name: string + file_path: string + file_size: number + file_type: string + file_ext: string + uploader_id: number + uploader_name?: string + upload_time: string + thumbnail_path?: string + share_code?: string + share_expire_time?: string + download_count: number + is_deleted: number + deleted_at?: string + deleted_by?: number + remark?: string + created_at: string + updated_at: string + download_url?: string + preview_url?: string + share_url?: string +} + +/** + * 图片项 + */ +export interface ImageItem { + url: string + name?: string +} + +/** + * 文件上传响应 + */ +export interface FileUploadResponse { + id: number + file_name: string + original_name: string + file_size: number + file_type: string + file_path: string + download_url: string + preview_url?: string + message: string +} + +/** + * 文件分享响应 + */ +export interface FileShareResponse { + share_code: string + share_url: string + expire_time: string +} + +/** + * 文件统计信息 + */ +export interface FileStatistics { + total_files: number + total_size: number + total_size_human: string + type_distribution: Record + upload_today: number + upload_this_week: number + upload_this_month: number + top_uploaders: Array<{ + uploader_id: number + count: number + }> +} + +/** + * 文件查询参数 + */ +export interface FileQueryParams { + keyword?: string + file_type?: string + uploader_id?: number + start_date?: string + end_date?: string + page?: number + page_size?: number +} + +/** + * 文件上传组件Props + */ +export interface FileUploadProps { + action?: string + showProgress?: boolean + showImagePreview?: boolean + drag?: boolean + multiple?: boolean + autoUpload?: boolean + limit?: number + maxSize?: number + accept?: string + data?: Record +} + +/** + * 文件上传组件Emits + */ +export interface FileUploadEmits { + (e: 'update:file-list', files: any[]): void + (e: 'upload-success', response: FileUploadResponse, file: any): void + (e: 'upload-error', error: Error, file: any): void + (e: 'upload-progress', event: any, file: any): void +} + +/** + * 图片预览组件Props + */ +export interface ImagePreviewProps { + visible: boolean + images: ImageItem[] + initialIndex?: number + showThumbnails?: boolean +} + +/** + * 图片预览组件Emits + */ +export interface ImagePreviewEmits { + (e: 'update:visible', value: boolean): void + (e: 'change', index: number): void +} + +/** + * 文件列表组件Props + */ +export interface FileListProps { + viewMode?: 'table' | 'grid' + showUpload?: boolean + selectable?: boolean +} + +/** + * 分片上传初始化参数 + */ +export interface ChunkUploadInitParams { + file_name: string + file_size: number + file_type: string + total_chunks: number + file_hash?: string +} + +/** + * 分片上传初始化响应 + */ +export interface ChunkUploadInitResponse { + upload_id: string + message: string +} + +/** + * 分片上传完成参数 + */ +export interface ChunkUploadCompleteParams { + upload_id: string + file_name: string + file_hash?: string +} + +/** + * 文件验证选项 + */ +export interface FileValidateOptions { + allowedTypes?: string[] + maxSize?: number + maxCount?: number +} + +/** + * 文件验证结果 + */ +export interface FileValidateResult { + valid: boolean + errors: string[] +} + +/** + * 文件类型枚举 + */ +export enum FileType { + IMAGE = 'image', + DOCUMENT = 'document', + ARCHIVE = 'archive', + VIDEO = 'video', + AUDIO = 'audio', + OTHER = 'other' +} + +/** + * 视图模式枚举 + */ +export enum ViewMode { + TABLE = 'table', + GRID = 'grid' +} + +/** + * 文件状态枚举 + */ +export enum FileStatus { + UPLOADING = 'uploading', + SUCCESS = 'success', + ERROR = 'error', + REMOVED = 'removed' +} + +/** + * 上传任务状态 + */ +export interface UploadTask { + uid: string + name: string + size: number + type: string + status: FileStatus + percentage: number + url?: string + response?: FileUploadResponse + error?: Error +} + +/** + * 文件分享创建参数 + */ +export interface FileShareCreateParams { + expire_days: number +} + +/** + * 文件批量删除参数 + */ +export interface FileBatchDeleteParams { + file_ids: number[] +} + +/** + * 文件更新参数 + */ +export interface FileUpdateParams { + remark?: string +} + +/** + * 缩略图生成选项 + */ +export interface ThumbnailOptions { + width?: number + height?: number + quality?: number +} + +/** + * 图片压缩选项 + */ +export interface ImageCompressOptions { + quality?: number + maxWidth?: number + maxHeight?: number +} + +/** + * 文件服务实例 + */ +export interface FileServiceInstance { + uploadFile(file: File, data?: { remark?: string }): Promise + getFileList(params?: FileQueryParams): Promise + getFileDetail(id: number): Promise + downloadFile(id: number): Promise + previewFile(id: number): Promise + updateFile(id: number, data: FileUpdateParams): Promise + deleteFile(id: number): Promise + deleteFilesBatch(fileIds: number[]): Promise + createShareLink(id: number, expireDays?: number): Promise + accessSharedFile(shareCode: string): Promise + getFileStatistics(uploaderId?: number): Promise + initChunkUpload(data: ChunkUploadInitParams): Promise + uploadChunk(uploadId: string, chunkIndex: number, chunk: Blob): Promise + completeChunkUpload(data: ChunkUploadCompleteParams): Promise +} + +/** + * 文件工具函数集合 + */ +export interface FileUtils { + formatFileSize(bytes: number): string + formatDateTime(dateString: string, format?: string): string + getFileExtension(filename: string): string + getFileNameWithoutExtension(filename: string): string + isImage(mimeType: string): boolean + isPDF(mimeType: string): boolean + isDocument(mimeType: string): boolean + isArchive(mimeType: string): boolean + getFileTypeIcon(mimeType: string): string + downloadFile(url: string, filename?: string): Promise + previewFile(url: string): void + validateFileType(file: File, allowedTypes: string[]): boolean + validateFileSize(file: File, maxSize: number): boolean + validateFiles(files: File[], options: FileValidateOptions): FileValidateResult + compressImage(file: File, quality?: number, maxWidth?: number, maxHeight?: number): Promise + createThumbnail(file: File, width?: number, height?: number): Promise + calculateFileHash(file: File): Promise + generateUniqueFilename(originalFilename: string): string +} diff --git a/src/components/form/DynamicFieldRenderer.vue b/src/components/form/DynamicFieldRenderer.vue new file mode 100644 index 0000000..6b37f65 --- /dev/null +++ b/src/components/form/DynamicFieldRenderer.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/src/components/form/FieldDesigner.vue b/src/components/form/FieldDesigner.vue new file mode 100644 index 0000000..830ffe5 --- /dev/null +++ b/src/components/form/FieldDesigner.vue @@ -0,0 +1,467 @@ + + + + + diff --git a/src/components/form/fields/BooleanField.vue b/src/components/form/fields/BooleanField.vue new file mode 100644 index 0000000..bc7e873 --- /dev/null +++ b/src/components/form/fields/BooleanField.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/components/form/fields/DateField.vue b/src/components/form/fields/DateField.vue new file mode 100644 index 0000000..0454532 --- /dev/null +++ b/src/components/form/fields/DateField.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/components/form/fields/MultiSelectField.vue b/src/components/form/fields/MultiSelectField.vue new file mode 100644 index 0000000..7f8a19f --- /dev/null +++ b/src/components/form/fields/MultiSelectField.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/form/fields/NumberField.vue b/src/components/form/fields/NumberField.vue new file mode 100644 index 0000000..5ed0ef7 --- /dev/null +++ b/src/components/form/fields/NumberField.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/components/form/fields/SelectField.vue b/src/components/form/fields/SelectField.vue new file mode 100644 index 0000000..5e2204b --- /dev/null +++ b/src/components/form/fields/SelectField.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/components/form/fields/TextField.vue b/src/components/form/fields/TextField.vue new file mode 100644 index 0000000..1108e91 --- /dev/null +++ b/src/components/form/fields/TextField.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/form/fields/TextareaField.vue b/src/components/form/fields/TextareaField.vue new file mode 100644 index 0000000..630c1e8 --- /dev/null +++ b/src/components/form/fields/TextareaField.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/components/statistics/StatCard.vue b/src/components/statistics/StatCard.vue new file mode 100644 index 0000000..0abdcc7 --- /dev/null +++ b/src/components/statistics/StatCard.vue @@ -0,0 +1,210 @@ + + + + + + + diff --git a/src/components/statistics/StatCardGroup.vue b/src/components/statistics/StatCardGroup.vue new file mode 100644 index 0000000..ce57c16 --- /dev/null +++ b/src/components/statistics/StatCardGroup.vue @@ -0,0 +1,78 @@ + + + + + + + diff --git a/src/components/statistics/index.ts b/src/components/statistics/index.ts new file mode 100644 index 0000000..b1773c2 --- /dev/null +++ b/src/components/statistics/index.ts @@ -0,0 +1,6 @@ +/** + * 统计组件统一导出 + */ + +export { default as StatCard } from './StatCard.vue' +export { default as StatCardGroup } from './StatCardGroup.vue' diff --git a/src/composables/useChartData.ts b/src/composables/useChartData.ts new file mode 100644 index 0000000..b631a1b --- /dev/null +++ b/src/composables/useChartData.ts @@ -0,0 +1,223 @@ +/** + * useChartData Composable + * + * 封装图表数据的加载、转换、缓存等操作 + */ + +import { ref, computed } from 'vue' +import type { Ref } from 'vue' + +/** + * 使用图表数据的 Composable + * + * @param apiMethod 数据加载方法 + */ +export function useChartData(apiMethod?: (params?: any) => Promise) { + const data = ref(null as unknown as T) + const loading = ref(false) + const error = ref(null) + const cache = ref>(new Map()) + const cacheExpiry = ref(5 * 60 * 1000) // 默认缓存5分钟 + + /** + * 加载数据 + */ + const loadData = async (params?: any, options?: { + useCache?: boolean + cacheKey?: string + showLoading?: boolean + }) => { + if (!apiMethod) { + console.warn('[useChartData] No API method provided') + return null + } + + const { + useCache: shouldUseCache = true, + cacheKey: customCacheKey, + showLoading: shouldShowLoading = true, + } = options || {} + + // 生成缓存键 + const cacheKey = customCacheKey || JSON.stringify(params || {}) + + // 检查缓存 + if (shouldUseCache) { + const cached = cache.value.get(cacheKey) + if (cached && Date.now() - cached.timestamp < cacheExpiry.value) { + console.log('[useChartData] Using cached data:', cacheKey) + data.value = cached.data + return cached.data + } + } + + // 加载数据 + if (shouldShowLoading) { + loading.value = true + } + error.value = null + + try { + const result = await apiMethod(params) + data.value = result + + // 更新缓存 + if (shouldUseCache) { + cache.value.set(cacheKey, { + data: result, + timestamp: Date.now(), + }) + } + + console.log('[useChartData] Data loaded successfully') + return result + } catch (err) { + const errorObj = err instanceof Error ? err : new Error(String(err)) + error.value = errorObj + console.error('[useChartData] Failed to load data:', errorObj) + throw errorObj + } finally { + if (shouldShowLoading) { + loading.value = false + } + } + } + + /** + * 刷新数据 + */ + const refresh = (params?: any) => { + return loadData(params, { useCache: false }) + } + + /** + * 清除缓存 + */ + const clearCache = (cacheKey?: string) => { + if (cacheKey) { + cache.value.delete(cacheKey) + console.log('[useChartData] Cache cleared for key:', cacheKey) + } else { + cache.value.clear() + console.log('[useChartData] All cache cleared') + } + } + + /** + * 设置缓存过期时间 + */ + const setCacheExpiry = (milliseconds: number) => { + cacheExpiry.value = milliseconds + } + + /** + * 重置状态 + */ + const reset = () => { + data.value = null as unknown as T + loading.value = false + error.value = null + } + + /** + * 转换数据为图表格式 + */ + const transformToChartData = ( + rawData: any[], + config: { + nameKey?: string + valueKey?: string + filter?: (item: any) => boolean + sort?: 'asc' | 'desc' | ((a: any, b: any) => number) + } = {} + ) => { + const { + nameKey = 'name', + valueKey = 'value', + filter, + sort, + } = config + + let result = rawData.map(item => ({ + name: item[nameKey], + value: item[valueKey], + ...item, + })) + + // 过滤 + if (filter) { + result = result.filter(filter) + } + + // 排序 + if (sort === 'asc') { + result.sort((a, b) => a.value - b.value) + } else if (sort === 'desc') { + result.sort((a, b) => b.value - a.value) + } else if (typeof sort === 'function') { + result.sort(sort) + } + + return result + } + + /** + * 计算百分比 + */ + const calculatePercentages = ( + items: Array<{ value: number }> + ) => { + const total = items.reduce((sum, item) => sum + item.value, 0) + return items.map(item => ({ + ...item, + percentage: total > 0 ? (item.value / total) * 100 : 0, + })) + } + + /** + * 分组聚合 + */ + const groupBy = ( + items: any[], + key: string, + aggregate: 'sum' | 'count' | 'avg' = 'count' + ) => { + const groups = new Map() + + items.forEach(item => { + const groupKey = item[key] + if (!groups.has(groupKey)) { + groups.set(groupKey, []) + } + groups.get(groupKey)!.push(item.value || 1) + }) + + return Array.from(groups.entries()).map(([name, values]) => { + let value: number + if (aggregate === 'sum') { + value = values.reduce((sum, v) => sum + v, 0) + } else if (aggregate === 'avg') { + value = values.reduce((sum, v) => sum + v, 0) / values.length + } else { + value = values.length + } + return { name, value } + }) + } + + return { + data, + loading, + error, + isLoaded: computed(() => data.value !== null), + hasError: computed(() => error.value !== null), + loadData, + refresh, + clearCache, + setCacheExpiry, + reset, + transformToChartData, + calculatePercentages, + groupBy, + } +} diff --git a/src/composables/useDynamicForm.ts b/src/composables/useDynamicForm.ts new file mode 100644 index 0000000..f788b12 --- /dev/null +++ b/src/composables/useDynamicForm.ts @@ -0,0 +1,245 @@ +/** + * 动态表单Composable + * 提供表单数据管理和验证功能 + */ + +import { ref, computed, reactive } from 'vue' +import type { FieldConfig, FormData, FormValidationState, FormActions } from '@/types/form' +import { validateFields } from '@/utils/fieldValidator' + +/** + * 动态表单Hook + * @param fields 字段配置列表 + */ +export function useDynamicForm(fields: FieldConfig[]) { + // 表单数据 + const formData = ref({}) + + // 验证错误 + const validationErrors = ref>({}) + + // 是否已修改 + const isDirty = ref(false) + + // 是否正在提交 + const isSubmitting = ref(false) + + // 初始数据(用于重置) + const initialData = ref({}) + + /** + * 表单是否有效 + */ + const isValid = computed(() => { + return Object.keys(validationErrors.value).length === 0 + }) + + /** + * 设置字段值 + */ + const setFieldValue = (fieldName: string, value: any) => { + formData.value = { + ...formData.value, + [fieldName]: value + } + isDirty.value = true + } + + /** + * 批量设置字段值 + */ + const setFieldValues = (values: Record) => { + formData.value = { + ...formData.value, + ...values + } + isDirty.value = true + } + + /** + * 获取字段值 + */ + const getFieldValue = (fieldName: string) => { + return formData.value[fieldName] + } + + /** + * 验证单个字段 + */ + const validateField = async (fieldName: string): Promise => { + const field = fields.find((f) => f.name === fieldName) + if (!field) return false + + const value = formData.value[fieldName] + const result = validateField(value, field, formData.value) + + if (!result.isValid) { + validationErrors.value = { + ...validationErrors.value, + [fieldName]: result.errors + } + } else { + const newErrors = { ...validationErrors.value } + delete newErrors[fieldName] + validationErrors.value = newErrors + } + + return result.isValid + } + + /** + * 验证所有字段 + */ + const validateAll = async (): Promise => { + const errors = validateFields(formData.value, fields) + validationErrors.value = errors + return Object.keys(errors).length === 0 + } + + /** + * 清除验证错误 + */ + const clearValidation = () => { + validationErrors.value = {} + } + + /** + * 清除单个字段的验证错误 + */ + const clearFieldValidation = (fieldName: string) => { + const newErrors = { ...validationErrors.value } + delete newErrors[fieldName] + validationErrors.value = newErrors + } + + /** + * 重置表单 + */ + const resetForm = () => { + formData.value = { ...initialData.value } + validationErrors.value = {} + isDirty.value = false + } + + /** + * 设置表单数据 + */ + const setFormData = (data: FormData) => { + formData.value = { ...data } + initialData.value = { ...data } + isDirty.value = false + } + + /** + * 获取表单数据 + */ + const getFormData = (): FormData => { + return { ...formData.value } + } + + /** + * 获取提交数据(只返回有值的字段) + */ + const getSubmitData = (): FormData => { + const submitData: FormData = {} + Object.keys(formData.value).forEach((key) => { + if (formData.value[key] !== undefined && formData.value[key] !== null && formData.value[key] !== '') { + submitData[key] = formData.value[key] + } + }) + return submitData + } + + /** + * 初始化默认值 + */ + const initDefaultValues = () => { + const defaultData: FormData = {} + fields.forEach((field) => { + if (field.defaultValue !== undefined) { + defaultData[field.name] = field.defaultValue + } + }) + formData.value = defaultData + initialData.value = { ...defaultData } + } + + /** + * 提交表单 + */ + const submitForm = async (handleSubmit: (data: FormData) => Promise | void) => { + // 验证表单 + const valid = await validateAll() + if (!valid) { + throw new Error('表单验证失败') + } + + isSubmitting.value = true + try { + await handleSubmit(getSubmitData()) + } finally { + isSubmitting.value = false + } + } + + // 初始化默认值 + initDefaultValues() + + return { + // 状态 + formData, + validationErrors, + isValid, + isDirty, + isSubmitting, + + // 方法 + setFieldValue, + setFieldValues, + getFieldValue, + validateField, + validateAll, + clearValidation, + clearFieldValidation, + resetForm, + setFormData, + getFormData, + getSubmitData, + submitForm + } +} + +/** + * 动态表单状态管理Hook(带Pinia集成) + * @param formId 表单唯一标识 + */ +export function useFormState(formId: string) { + const formStates = reactive>(new Map()) + + /** + * 保存表单状态 + */ + const saveState = (data: FormData) => { + formStates.set(formId, { ...data }) + } + + /** + * 获取表单状态 + */ + const getState = (): FormData | undefined => { + return formStates.get(formId) + } + + /** + * 清除表单状态 + */ + const clearState = () => { + formStates.delete(formId) + } + + return { + saveState, + getState, + clearState + } +} diff --git a/src/composables/useECharts.ts b/src/composables/useECharts.ts new file mode 100644 index 0000000..4983aaf --- /dev/null +++ b/src/composables/useECharts.ts @@ -0,0 +1,210 @@ +/** + * useECharts Composable + * + * 封装 ECharts 初始化、更新、销毁等操作 + */ + +import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue' +import * as echarts from 'echarts' +import type { EChartOption, ECharts } from 'echarts' +import { resizeChart } from '@/utils/echarts' + +/** + * 使用 ECharts 的 Composable + * + * @param chartRef 图表容器引用 + * @param theme 图表主题 + */ +export function useECharts( + chartRef: Ref, + theme?: string | object +) { + const chart = ref(null) + const loading = ref(false) + const isReady = ref(false) + + /** + * 初始化图表 + */ + const initChart = () => { + if (!chartRef.value) { + console.warn('[useECharts] Chart container not found') + return + } + + try { + // 销毁已存在的图表实例 + if (chart.value) { + chart.value.dispose() + } + + // 创建新实例 + chart.value = echarts.init(chartRef.value, theme) + isReady.value = true + + console.log('[useECharts] Chart initialized successfully') + } catch (error) { + console.error('[useECharts] Failed to initialize chart:', error) + isReady.value = false + } + } + + /** + * 设置图表配置 + */ + const setOption = (option: EChartOption, notMerge?: boolean, lazyUpdate?: boolean) => { + if (!chart.value || !isReady.value) { + console.warn('[useECharts] Chart not ready, skipping setOption') + return + } + + try { + chart.value.setOption(option, notMerge, lazyUpdate) + console.log('[useECharts] Option set successfully') + } catch (error) { + console.error('[useECharts] Failed to set option:', error) + } + } + + /** + * 显示加载动画 + */ + const showLoading = (config?: { + text?: string + color?: string + textColor?: string + maskColor?: string + }) => { + if (!chart.value) return + + loading.value = true + chart.value.showLoading('default', { + text: config?.text || '加载中...', + color: config?.color || '#475569', + textColor: config?.textColor || '#1e293b', + maskColor: config?.maskColor || 'rgba(255, 255, 255, 0.8)', + zlevel: 0, + }) + } + + /** + * 隐藏加载动画 + */ + const hideLoading = () => { + if (!chart.value) return + + loading.value = false + chart.value.hideLoading() + } + + /** + * 调整图表尺寸 + */ + const resize = (delay?: number) => { + if (!chart.value) return + + resizeChart(chart.value, delay) + } + + /** + * 销毁图表 + */ + const dispose = () => { + if (chart.value) { + chart.value.dispose() + chart.value = null + isReady.value = false + console.log('[useECharts] Chart disposed') + } + } + + /** + * 清空图表 + */ + const clear = () => { + if (chart.value) { + chart.value.clear() + console.log('[useECharts] Chart cleared') + } + } + + /** + * 获取图表实例 + */ + const getInstance = () => { + return chart.value + } + + /** + * 绑定图表事件 + */ + const on = (eventName: string, handler: Function) => { + if (!chart.value) return + + chart.value.on(eventName, handler) + } + + /** + * 解绑图表事件 + */ + const off = (eventName: string, handler?: Function) => { + if (!chart.value) return + + chart.value.off(eventName, handler) + } + + /** + * 导出图表图片 + */ + const getDataURL = (opts?: { + type?: 'png' | 'jpeg' | 'svg' + pixelRatio?: number + backgroundColor?: string + }) => { + if (!chart.value) return null + + return chart.value.getDataURL({ + type: opts?.type || 'png', + pixelRatio: opts?.pixelRatio || 1, + backgroundColor: opts?.backgroundColor || '#fff', + }) + } + + /** + * 监听窗口大小变化 + */ + const handleResize = () => { + resize() + } + + // 初始化 + onMounted(() => { + nextTick(() => { + initChart() + window.addEventListener('resize', handleResize) + }) + }) + + // 清理 + onBeforeUnmount(() => { + window.removeEventListener('resize', handleResize) + dispose() + }) + + return { + chart, + loading, + isReady, + initChart, + setOption, + showLoading, + hideLoading, + resize, + dispose, + clear, + getInstance, + on, + off, + getDataURL, + } +} diff --git a/src/composables/useFieldConfig.ts b/src/composables/useFieldConfig.ts new file mode 100644 index 0000000..1efe334 --- /dev/null +++ b/src/composables/useFieldConfig.ts @@ -0,0 +1,179 @@ +/** + * 字段配置管理Composable + * 用于加载和缓存设备类型的字段配置 + */ + +import { ref } from 'vue' +import type { FieldConfig } from '@/types/form' +import request from '@/utils/request' + +/** + * 字段配置管理Hook + */ +export function useFieldConfig() { + // 字段配置缓存 + const fieldConfigs = ref>({}) + + // 加载状态 + const loading = ref(false) + + // 错误信息 + const error = ref(null) + + /** + * 从API加载字段配置 + * @param deviceTypeId 设备类型ID + */ + const loadFieldConfig = async (deviceTypeId: string | number): Promise => { + // 检查缓存 + const cached = getCachedFieldConfig(deviceTypeId) + if (cached) { + return cached + } + + loading.value = true + error.value = null + + try { + // 调用API获取字段配置 + const response = await request({ + url: `/device-types/${deviceTypeId}/fields`, + method: 'get' + }) + + // 转换API字段配置为前端字段配置 + const fields = transformApiFieldsToFieldConfigs(response) + + // 缓存字段配置 + fieldConfigs.value[deviceTypeId] = fields + + return fields + } catch (err: any) { + error.value = err.message || '加载字段配置失败' + console.error('加载字段配置失败:', err) + throw err + } finally { + loading.value = false + } + } + + /** + * 从缓存获取字段配置 + * @param deviceTypeId 设备类型ID + */ + const getCachedFieldConfig = (deviceTypeId: string | number): FieldConfig[] | null => { + return fieldConfigs.value[deviceTypeId] || null + } + + /** + * 批量加载字段配置 + * @param deviceTypeIds 设备类型ID列表 + */ + const loadFieldConfigs = async (deviceTypeIds: Array): Promise => { + const promises = deviceTypeIds.map((id) => loadFieldConfig(id)) + await Promise.all(promises) + } + + /** + * 清除缓存 + * @param deviceTypeId 设备类型ID(不传则清除所有缓存) + */ + const clearCache = (deviceTypeId?: string | number) => { + if (deviceTypeId) { + delete fieldConfigs.value[deviceTypeId] + } else { + fieldConfigs.value = {} + } + } + + /** + * 设置字段配置(用于手动设置) + * @param deviceTypeId 设备类型ID + * @param fields 字段配置 + */ + const setFieldConfig = (deviceTypeId: string | number, fields: FieldConfig[]) => { + fieldConfigs.value[deviceTypeId] = fields + } + + /** + * 转换API字段配置为前端字段配置 + */ + const transformApiFieldsToFieldConfigs = (apiFields: any[]): FieldConfig[] => { + return apiFields.map((apiField) => ({ + id: String(apiField.id), + name: apiField.fieldCode, + label: apiField.fieldName, + fieldType: transformFieldType(apiField.fieldType), + required: apiField.isRequired || false, + placeholder: apiField.placeholder, + options: apiField.options || [], + validationRules: transformValidationRules(apiField.validationRules), + defaultValue: apiField.defaultValue, + span: 24, // 默认占满一行 + description: apiField.description + })) + } + + /** + * 转换字段类型 + */ + const transformFieldType = (apiType: string): FieldConfig['fieldType'] => { + const typeMap: Record = { + text: 'text', + textarea: 'textarea', + number: 'number', + date: 'date', + select: 'select', + multiselect: 'multiselect', + checkbox: 'boolean', + boolean: 'boolean', + url: 'url', + email: 'email', + phone: 'phone' + } + + return typeMap[apiType] || 'text' + } + + /** + * 转换验证规则 + */ + const transformValidationRules = (apiRules?: any) => { + if (!apiRules) return undefined + + return { + min: apiRules.min_length || apiRules.min, + max: apiRules.max_length || apiRules.max, + pattern: apiRules.pattern + } + } + + return { + // 状态 + fieldConfigs, + loading, + error, + + // 方法 + loadFieldConfig, + loadFieldConfigs, + getCachedFieldConfig, + setFieldConfig, + clearCache + } +} + +/** + * 全局字段配置管理单例 + */ +let globalFieldConfigInstance: ReturnType | null = null + +/** + * 获取全局字段配置管理实例 + */ +export function getGlobalFieldConfigManager() { + if (!globalFieldConfigInstance) { + globalFieldConfigInstance = useFieldConfig() + } + return globalFieldConfigInstance +} diff --git a/src/composables/usePagination.ts b/src/composables/usePagination.ts new file mode 100644 index 0000000..61bb837 --- /dev/null +++ b/src/composables/usePagination.ts @@ -0,0 +1,38 @@ +/** + * 分页组合式函数 + */ +import { ref, reactive, computed } from 'vue' + +export function usePagination(defaultPageSize = 20) { + const currentPage = ref(1) + const pageSize = ref(defaultPageSize) + const total = ref(0) + + const pagination = reactive({ + page: currentPage, + pageSize: pageSize, + total: total + }) + + const totalPages = computed(() => { + return Math.ceil(total.value / pageSize.value) + }) + + const resetPage = () => { + currentPage.value = 1 + } + + const setTotal = (count: number) => { + total.value = count + } + + return { + currentPage, + pageSize, + total, + pagination, + totalPages, + resetPage, + setTotal + } +} diff --git a/src/composables/useTable.ts b/src/composables/useTable.ts new file mode 100644 index 0000000..613d3f5 --- /dev/null +++ b/src/composables/useTable.ts @@ -0,0 +1,88 @@ +/** + * 表格组合式函数 + */ +import { ref, reactive } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' + +export function useTable(fetchApi: any) { + const loading = ref(false) + const tableData = ref([]) + const selectedRows = ref([]) + + const filters = reactive>({}) + + /** + * 获取数据 + */ + const fetchData = async (params: any = {}) => { + loading.value = true + try { + const data = await fetchApi({ + ...filters, + ...params + }) + tableData.value = data.items || [] + return data + } catch (error) { + ElMessage.error('获取数据失败') + throw error + } finally { + loading.value = false + } + } + + /** + * 搜索 + */ + const handleSearch = () => { + return fetchData({ page: 1 }) + } + + /** + * 重置 + */ + const handleReset = () => { + Object.keys(filters).forEach(key => { + filters[key] = undefined + }) + return fetchData({ page: 1 }) + } + + /** + * 选择变化 + */ + const handleSelectionChange = (selection: any[]) => { + selectedRows.value = selection + } + + /** + * 删除确认 + */ + const handleDeleteConfirm = async (callback: () => Promise) => { + try { + await ElMessageBox.confirm('确定要删除吗?', '提示', { + type: 'warning' + }) + await callback() + ElMessage.success('删除成功') + return true + } catch (error) { + if (error !== 'cancel') { + ElMessage.error('删除失败') + } + return false + } + } + + return { + loading, + tableData, + selectedRows, + filters, + fetchData, + handleSearch, + handleReset, + handleSelectionChange, + handleDeleteConfirm + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..1aa6b05 --- /dev/null +++ b/src/index.html @@ -0,0 +1,13 @@ + + + + + + + 资产管理系统 + + +
+ + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue new file mode 100644 index 0000000..967b5d9 --- /dev/null +++ b/src/layouts/MainLayout.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..f439cfa --- /dev/null +++ b/src/main.ts @@ -0,0 +1,26 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +import App from './App.vue' +import router from './router' +import '@/assets/styles/index.scss' + +const app = createApp(App) + +// 注册 Element Plus 图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(createPinia()) +app.use(router) +app.use(ElementPlus, { + locale: zhCn +}) + +app.mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..6992110 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,278 @@ +/** + * 路由配置 + */ +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' +import { useUserStore } from '@/stores' + +const routes: RouteRecordRaw[] = [ + { + path: '/login', + name: 'Login', + component: () => import('@/views/auth/Login.vue'), + meta: { + title: '登录', + hidden: true + } + }, + { + path: '/', + component: () => import('@/layouts/MainLayout.vue'), + redirect: '/assets/list', + children: [ + // 后台管理 + { + path: '/admin', + name: 'Admin', + redirect: '/admin/users', + meta: { + title: '后台管理', + icon: 'Setting' + }, + children: [ + { + path: 'users', + name: 'UserManagement', + component: () => import('@/views/admin/UserManagement.vue'), + meta: { + title: '用户管理', + icon: 'User' + } + }, + { + path: 'roles', + name: 'RoleManagement', + component: () => import('@/views/admin/RoleManagement.vue'), + meta: { + title: '角色权限', + icon: 'Lock' + } + }, + { + path: 'device-types', + name: 'DeviceTypeManagement', + component: () => import('@/views/admin/DeviceTypeManagement.vue'), + meta: { + title: '设备类型', + icon: 'Grid' + } + }, + { + path: 'organizations', + name: 'OrganizationManagement', + component: () => import('@/views/admin/OrganizationManagement.vue'), + meta: { + title: '机构网点', + icon: 'OfficeBuilding' + } + } + ] + }, + // 资产管理 + { + path: '/assets', + name: 'Assets', + redirect: '/assets/list', + meta: { + title: '资产管理', + icon: 'Box' + }, + children: [ + { + path: 'list', + name: 'AssetList', + component: () => import('@/views/assets/AssetList.vue'), + meta: { + title: '资产列表', + icon: 'List' + } + }, + { + path: 'create', + name: 'AssetCreate', + component: () => import('@/views/assets/AssetCreate.vue'), + meta: { + title: '资产入库', + icon: 'Plus' + } + }, + { + path: 'allocation', + name: 'AssetAllocation', + component: () => import('@/views/assets/AssetAllocation.vue'), + meta: { + title: '资产分配', + icon: 'Share' + } + }, + { + path: 'scan', + name: 'AssetScan', + component: () => import('@/views/assets/AssetScan.vue'), + meta: { + title: '扫码查询', + icon: 'Camera' + } + }, + { + path: 'maintenance', + name: 'MaintenanceManagement', + component: () => import('@/views/assets/MaintenanceManagement.vue'), + meta: { + title: '维修管理', + icon: 'Tools' + } + }, + { + path: 'statistics', + name: 'StatisticsDashboard', + component: () => import('@/views/assets/StatisticsDashboard.vue'), + meta: { + title: '统计报表', + icon: 'DataAnalysis' + } + } + ] + }, + // 调拨管理 + { + path: '/allocation', + name: 'Allocation', + redirect: '/allocation/transfers', + meta: { + title: '调拨管理', + icon: 'Sort' + }, + children: [ + { + path: 'transfers', + name: 'TransferList', + component: () => import('@/views/allocation/TransferList.vue'), + meta: { + title: '资产调拨', + icon: 'ArrowRight' + } + }, + { + path: 'recoveries', + name: 'RecoveryList', + component: () => import('@/views/allocation/RecoveryList.vue'), + meta: { + title: '资产回收', + icon: 'ArrowLeft' + } + } + ] + }, + // 系统管理 + { + path: '/system', + name: 'System', + redirect: '/system/config', + meta: { + title: '系统管理', + icon: 'Setting' + }, + children: [ + { + path: 'config', + name: 'SystemConfig', + component: () => import('@/views/system/SystemConfig.vue'), + meta: { + title: '系统配置', + icon: 'Tools' + } + }, + { + path: 'logs', + name: 'OperationLog', + component: () => import('@/views/system/OperationLog.vue'), + meta: { + title: '操作日志', + icon: 'Document' + } + }, + { + path: 'notification', + name: 'NotificationCenter', + component: () => import('@/views/system/NotificationCenter.vue'), + meta: { + title: '消息通知', + icon: 'Bell' + } + } + ] + }, + // 示例页面 + { + path: '/examples', + name: 'Examples', + redirect: '/examples/charts', + meta: { + title: '示例', + icon: 'Document' + }, + children: [ + { + path: 'charts', + name: 'ChartsExample', + component: () => import('@/views/examples/ChartsExample.vue'), + meta: { + title: '图表示例', + icon: 'PieChart' + } + } + ] + } + ] + }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: () => import('@/views/error/404.vue'), + meta: { + title: '404', + hidden: true + } + } +] + +const router = createRouter({ + history: createWebHistory(), + routes, + scrollBehavior() { + return { top: 0 } + } +}) + +// 路由守卫 +router.beforeEach((to, from, next) => { + const userStore = useUserStore() + + // 设置页面标题 + document.title = `${to.meta.title || '资产管理系统'} - 资产管理系统` + + // 不需要登录的页面 + const whiteList = ['/login'] + + if (userStore.isLoggedIn) { + // 已登录 + if (to.path === '/login') { + next({ path: '/' }) + } else { + next() + } + } else { + // 未登录,直接跳转到登录页,不显示任何提示 + if (whiteList.includes(to.path)) { + next() + } else { + next({ path: '/login', query: { redirect: to.fullPath } }) + } + } +}) + +router.afterEach(() => { + // 路由跳转后的处理 +}) + +export default router diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..278580f --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,5 @@ +/** + * Store 统一导出 + */ +export { useUserStore } from './modules/user' +export { useAppStore } from './modules/app' diff --git a/src/stores/modules/app.ts b/src/stores/modules/app.ts new file mode 100644 index 0000000..7d9d9bf --- /dev/null +++ b/src/stores/modules/app.ts @@ -0,0 +1,68 @@ +/** + * 应用状态管理 + */ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useAppStore = defineStore('app', () => { + // 侧边栏状态 + const sidebarCollapsed = ref(false) + + // 设备类型 + const deviceTypes = ref([]) + + // 网点树 + const organizationTree = ref([]) + + // 未读消息数 + const unreadCount = ref(0) + + /** + * 切换侧边栏 + */ + const toggleSidebar = () => { + sidebarCollapsed.value = !sidebarCollapsed.value + } + + /** + * 设置侧边栏状态 + */ + const setSidebarCollapsed = (collapsed: boolean) => { + sidebarCollapsed.value = collapsed + } + + /** + * 设置设备类型 + */ + const setDeviceTypes = (types: any[]) => { + deviceTypes.value = types + } + + /** + * 设置网点树 + */ + const setOrganizationTree = (tree: any[]) => { + organizationTree.value = tree + } + + /** + * 设置未读消息数 + */ + const setUnreadCount = (count: number) => { + unreadCount.value = count + } + + return { + // 状态 + sidebarCollapsed, + deviceTypes, + organizationTree, + unreadCount, + // 方法 + toggleSidebar, + setSidebarCollapsed, + setDeviceTypes, + setOrganizationTree, + setUnreadCount + } +}) diff --git a/src/stores/modules/user.ts b/src/stores/modules/user.ts new file mode 100644 index 0000000..869fc37 --- /dev/null +++ b/src/stores/modules/user.ts @@ -0,0 +1,97 @@ +/** + * 用户状态管理 + */ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import type { UserInfo } from '@/types' +import { login as loginApi, logout as logoutApi } from '@/api' +import { getToken, setToken, removeToken } from '@/utils/auth' + +export const useUserStore = defineStore('user', () => { + // 状态 + const token = ref(getToken() || '') + const userInfo = ref(null) + + // 计算属性 + const isLoggedIn = computed(() => !!token.value) + const userName = computed(() => userInfo.value?.realName || '') + const userAvatar = computed(() => userInfo.value?.avatar || '') + const permissions = computed(() => userInfo.value?.permissions || []) + const roles = computed(() => userInfo.value?.roles || []) + + /** + * 登录 + */ + const login = async (username: string, password: string, captcha: string, captchaKey: string) => { + const data = await loginApi({ + username, + password, + captcha, + captcha_key: captchaKey + }) + + token.value = data.access_token + userInfo.value = data.user + setToken(data.access_token) + + return data + } + + /** + * 登出 + */ + const logout = async () => { + try { + await logoutApi() + } finally { + token.value = '' + userInfo.value = null + removeToken() + } + } + + /** + * 设置用户信息 + */ + const setUserInfo = (info: UserInfo) => { + userInfo.value = info + } + + /** + * 检查权限 + */ + const hasPermission = (permission: string) => { + if (userInfo.value?.isAdmin) { + return true + } + return permissions.value.includes(permission) + } + + /** + * 检查角色 + */ + const hasRole = (roleCode: string) => { + if (userInfo.value?.isAdmin) { + return true + } + return roles.value.some(role => role.roleCode === roleCode) + } + + return { + // 状态 + token, + userInfo, + // 计算属性 + isLoggedIn, + userName, + userAvatar, + permissions, + roles, + // 方法 + login, + logout, + setUserInfo, + hasPermission, + hasRole + } +}) diff --git a/src/types/charts.ts b/src/types/charts.ts new file mode 100644 index 0000000..c9cb73e --- /dev/null +++ b/src/types/charts.ts @@ -0,0 +1,193 @@ +/** + * 图表相关类型定义 + */ + +import type { EChartOption } from 'echarts' + +/** 图表数据项 */ +export interface ChartDataItem { + name: string + value: number + [key: string]: any +} + +/** 图表系列数据 */ +export interface ChartSeries { + name: string + data: number[] + color?: string + [key: string]: any +} + +/** 饼图配置 */ +export interface PieChartConfig { + data: ChartDataItem[] + title?: string + type?: 'pie' | 'doughnut' + showLegend?: boolean + showLabel?: boolean + height?: string + onClick?: (item: ChartDataItem) => void +} + +/** 柱状图配置 */ +export interface BarChartConfig { + data: ChartDataItem[] + title?: string + type?: 'vertical' | 'horizontal' + stacked?: boolean + grouped?: boolean + xAxisLabel?: string + yAxisLabel?: string + height?: string + onClick?: (item: ChartDataItem) => void +} + +/** 折线图配置 */ +export interface LineChartConfig { + data: ChartDataItem[] + series?: ChartSeries[] + title?: string + area?: boolean + smooth?: boolean + xAxisLabel?: string + yAxisLabel?: string + height?: string + onClick?: (item: ChartDataItem) => void +} + +/** 仪表盘配置 */ +export interface GaugeChartConfig { + value: number + min?: number + max?: number + title?: string + unit?: string + height?: string + color?: string[] +} + +/** 漏斗图配置 */ +export interface FunnelChartConfig { + data: ChartDataItem[] + title?: string + height?: string + onClick?: (item: ChartDataItem) => void +} + +/** 统计卡片配置 */ +export interface StatCardConfig { + title: string + value: number | string + unit?: string + icon?: string + trend?: 'up' | 'down' | 'flat' + trendValue?: number + color?: string + loading?: boolean + clickable?: boolean + onClick?: () => void +} + +/** 图表主题 */ +export type ChartTheme = 'default' | 'dark' | 'custom' + +/** 图表尺寸 */ +export type ChartSize = 'small' | 'medium' | 'large' + +/** 图表事件 */ +export interface ChartEvents { + onClick?: (params: any) => void + onDoubleClick?: (params: any) => void + onMouseOver?: (params: any) => void + onMouseOut?: (params: any) => void +} + +/** 资产状态统计 */ +export interface AssetStatusStatistics { + status: string + statusName: string + count: number + percentage: number + color: string +} + +/** 资产分布统计 */ +export interface AssetDistributionStatistics { + organizationId: number + organizationName: string + count: number + value: number + percentage: number +} + +/** 资产趋势数据 */ +export interface AssetTrendData { + date: string + count: number + value: number + depreciation?: number + netValue?: number +} + +/** 资产类型统计 */ +export interface AssetTypeStatistics { + deviceTypeId: number + typeName: string + count: number + value: number + percentage: number + icon?: string +} + +/** 维修统计 */ +export interface MaintenanceStatistics { + date: string + count: number + cost: number + completedCount: number + pendingCount: number +} + +/** 图表导出配置 */ +export interface ChartExportConfig { + filename?: string + type?: 'png' | 'jpeg' | 'svg' + quality?: number + pixelRatio?: number + backgroundColor?: string +} + +/** 图表响应式配置 */ +export interface ChartResponsiveConfig { + width?: number | string + height?: number | string + autoResize?: boolean + resizeDelay?: number +} + +/** 图表加载状态 */ +export interface ChartLoadingConfig { + show?: boolean + text?: string + color?: string + textColor?: string + maskColor?: string + zlevel?: number +} + +/** 图表动画配置 */ +export interface ChartAnimationConfig { + enable?: boolean + duration?: number + easing?: string + delay?: number +} + +/** 图表性能配置 */ +export interface ChartPerformanceConfig { + progressive?: number + progressiveThreshold?: number + hoverLayerThreshold?: number + useUTC?: boolean +} diff --git a/src/types/form.ts b/src/types/form.ts new file mode 100644 index 0000000..69d07ee --- /dev/null +++ b/src/types/form.ts @@ -0,0 +1,187 @@ +/** + * 动态表单类型定义 + * 用于动态字段渲染器和表单设计器 + */ + +/** 字段类型枚举 */ +export type FieldType = + | 'text' // 单行文本 + | 'textarea' // 多行文本 + | 'number' // 数字输入 + | 'date' // 日期选择 + | 'select' // 下拉选择 + | 'multiselect' // 多选下拉 + | 'boolean' // 开关/复选框 + | 'url' // URL链接 + | 'email' // 邮箱 + | 'phone' // 手机号 + | 'tree' // 树形选择 + +/** 字段验证规则 */ +export interface ValidationRules { + /** 最小值/最小长度 */ + min?: number + /** 最大值/最大长度 */ + max?: number + /** 正则表达式 */ + pattern?: string + /** 自定义验证函数 */ + custom?: (value: any, allData: Record) => boolean | string + /** 自定义错误消息 */ + customMessage?: string +} + +/** 字段配置 */ +export interface FieldConfig { + /** 字段唯一标识 */ + id: string + /** 字段名称(用于提交数据) */ + name: string + /** 字段标签(显示名称) */ + label: string + /** 字段类型 */ + fieldType: FieldType + /** 是否必填 */ + required?: boolean + /** 默认值 */ + defaultValue?: any + /** 占位符文本 */ + placeholder?: string + /** 选项(用于select/multiselect) */ + options?: Array<{ label: string; value: any; disabled?: boolean }> + /** 验证规则 */ + validationRules?: ValidationRules + /** 栅格占列数(1-24) */ + span?: number + /** 是否显示(支持函数动态控制) */ + visible?: boolean | ((data: Record) => boolean) + /** 是否禁用(支持函数动态控制) */ + disabled?: boolean | ((data: Record) => boolean) + /** 字段描述 */ + description?: string + /** 自定义类名 */ + className?: string + /** 树形数据(用于tree类型) */ + treeData?: TreeNode[] + /** 是否多选(用于tree类型) */ + multiple?: boolean +} + +/** 树形节点 */ +export interface TreeNode { + id: string | number + label: string + children?: TreeNode[] + disabled?: boolean +} + +/** 表单数据 */ +export type FormData = Record + +/** 验证结果 */ +export interface ValidationResult { + /** 是否验证通过 */ + isValid: boolean + /** 错误信息 */ + errors: string[] +} + +/** 表单验证状态 */ +export interface FormValidationState { + /** 整个表单是否有效 */ + isValid: boolean + /** 字段级错误信息 */ + errors: Record +} + +/** 字段变更事件 */ +export interface FieldChangeEvent { + /** 字段名称 */ + fieldName: string + /** 字段值 */ + value: any + /** 旧值 */ + oldValue?: any +} + +/** 字段联动配置 */ +export interface FieldDependency { + /** 源字段(触发联动的字段) */ + sourceField: string + /** 目标字段(被联动的字段) */ + targetField: string + /** 联动类型 */ + type: 'show' | 'hide' | 'enable' | 'disable' | 'setValue' | 'setOptions' + /** 触发条件函数 */ + condition: (sourceValue: any, allData: Record) => boolean + /** 联动动作 */ + action?: (targetValue: any, sourceValue: any, allData: Record) => any +} + +/** 表单组件Props */ +export interface DynamicFormProps { + /** 设备类型ID */ + deviceTypeId?: string | number + /** 字段配置列表 */ + fields: FieldConfig[] + /** 表单数据(v-model) */ + modelValue: FormData + /** 是否只读模式 */ + readonly?: boolean + /** 标签宽度 */ + labelWidth?: string | number + /** 标签位置 */ + labelPosition?: 'left' | 'right' | 'top' + /** 栅格间隔 */ + gutter?: number + /** 字段联动配置 */ + dependencies?: FieldDependency[] +} + +/** 表单组件Emits */ +export interface DynamicFormEmits { + /** 更新表单数据 */ + (e: 'update:modelValue', value: FormData): void + /** 字段值变更 */ + (e: 'field-change', event: FieldChangeEvent): void + /** 验证状态变更 */ + (e: 'validation-change', state: FormValidationState): void + /** 表单提交 */ + (e: 'submit', data: FormData): void +} + +/** 字段设计器Props */ +export interface FieldDesignerProps { + /** 字段配置列表(v-model) */ + modelValue: FieldConfig[] + /** 是否只读模式 */ + readonly?: boolean +} + +/** 字段设计器Emits */ +export interface FieldDesignerEmits { + /** 更新字段配置 */ + (e: 'update:modelValue', value: FieldConfig[]): void + /** 预览字段配置 */ + (e: 'preview', fields: FieldConfig[]): void +} + +/** 表单操作方法 */ +export interface FormActions { + /** 设置字段值 */ + setFieldValue: (field: string, value: any) => void + /** 获取字段值 */ + getFieldValue: (field: string) => any + /** 验证单个字段 */ + validateField: (field: string) => Promise + /** 验证整个表单 */ + validateAll: () => Promise + /** 重置表单 */ + resetForm: () => void + /** 设置表单数据 */ + setFormData: (data: FormData) => void + /** 获取表单数据 */ + getFormData: () => FormData + /** 清除验证 */ + clearValidation: () => void +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..2ca038e --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,254 @@ +/** + * 通用类型定义 + */ + +/** API 响应结构 */ +export interface ApiResponse { + code: number + message: string + data: T + timestamp?: number +} + +/** 分页参数 */ +export interface PaginationParams { + page?: number + page_size?: number + sort_by?: string + sort_order?: 'asc' | 'desc' +} + +/** 分页响应 */ +export interface PaginationResponse { + total: number + page: number + page_size: number + total_pages: number + items: T[] +} + +/** 用户信息 */ +export interface UserInfo { + id: number + username: string + realName: string + email?: string + phone?: string + avatar?: string + status: 'active' | 'disabled' | 'locked' + isAdmin: boolean + roles?: Role[] + permissions?: string[] + lastLoginAt?: string + createdAt: string +} + +/** 角色 */ +export interface Role { + id: number + roleCode: string + roleName: string + description?: string + status: string + sortOrder: number + userCount?: number + permissions?: Permission[] +} + +/** 权限 */ +export interface Permission { + id: number + moduleName?: string + permissionName: string + permissionCode: string + children?: Permission[] +} + +/** 资产状态 */ +export type AssetStatus = + | 'pending' + | 'in_stock' + | 'in_use' + | 'transferring' + | 'maintenance' + | 'pending_scrap' + | 'scrapped' + | 'lost' + +/** 资产实体 */ +export interface Asset { + id: number + assetCode: string + assetName: string + deviceTypeId: number + deviceType: { + id: number + typeName: string + category?: string + } + brandId?: number + brand?: { + id: number + brandName: string + } + model?: string + serialNumber?: string + supplierId?: number + supplier?: { + id: number + supplierName: string + } + organizationId: number + organization: { + id: number + orgName: string + } + location?: string + status: AssetStatus + statusName?: string + purchaseDate?: string + purchasePrice?: number + warrantyExpireDate?: string + qrCodeUrl?: string + dynamicAttributes: Record + statusHistory?: AssetStatusHistory[] + createdAt: string + updatedAt: string + createdBy?: string +} + +/** 资产状态历史 */ +export interface AssetStatusHistory { + id: number + assetId: number + oldStatus?: string + newStatus: string + operationType: string + operatorName?: string + remark?: string + createdAt: string +} + +/** 设备类型 */ +export interface DeviceType { + id: number + typeCode: string + typeName: string + category: string + description?: string + icon?: string + status: string + sortOrder: number + fieldCount?: number +} + +/** 动态字段 */ +export interface DynamicField { + id: number + fieldCode: string + fieldName: string + fieldType: 'text' | 'textarea' | 'number' | 'date' | 'select' | 'checkbox' | 'url' | 'email' | 'phone' + isRequired: boolean + placeholder?: string + options?: Array<{ label: string; value: any }> + validationRules?: Record + defaultValue?: any + sortOrder: number +} + +/** 机构网点 */ +export interface Organization { + id: number + orgCode: string + orgName: string + orgType: 'province' | 'city' | 'outlet' + parentId?: number + treeLevel: number + address?: string + contactPerson?: string + contactPhone?: string + children?: Organization[] +} + +/** 分配单 */ +export interface AllocationOrder { + id: number + orderCode: string + orderType: 'allocation' | 'transfer' | 'recovery' | 'maintenance' | 'scrap' + orderTypeName: string + title: string + targetOrganizationId?: number + targetOrganization?: Organization + applicantId: number + applicant?: UserInfo + approvalStatus: 'pending' | 'approved' | 'rejected' | 'cancelled' + approvalStatusName: string + executeStatus: 'pending' | 'executing' | 'completed' | 'failed' + assetCount: number + remark?: string + approvalRemark?: string + createdAt: string +} + +/** 统计概览 */ +export interface StatisticsOverview { + totalAssets: number + totalValue: number + assetsInStock: number + assetsInUse: number + assetsMaintenance: number + assetsScrapped: number + organizationDistribution: Array<{ + orgName: string + count: number + value: number + }> + deviceTypeDistribution: Array<{ + typeName: string + count: number + }> +} + +/** 维修记录 */ +export interface MaintenanceRecord { + id: number + assetId: number + asset?: Asset + faultDescription: string + faultType: 'hardware' | 'software' | 'other' + priority: 'low' | 'medium' | 'high' + maintenanceType: 'self_repair' | 'vendor_repair' + vendorId?: number + status: 'pending' | 'in_progress' | 'completed' | 'closed' + cost?: number + completedAt?: string + createdAt: string +} + +/** 表单项配置 */ +export interface FormItemConfig { + prop: string + label: string + type: 'input' | 'select' | 'date' | 'number' | 'textarea' + placeholder?: string + options?: Array<{ label: string; value: any }> + required?: boolean + rules?: any[] + span?: number +} + +/** 菜单项 */ +export interface MenuItem { + path: string + name: string + icon?: string + title: string + redirect?: string + children?: MenuItem[] + hidden?: boolean + meta?: { + title: string + icon?: string + permissions?: string[] + } +} diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..65d4ce8 --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,42 @@ +/** + * 认证相关工具函数 + */ + +const TOKEN_KEY = 'access_token' +const REFRESH_TOKEN_KEY = 'refresh_token' + +/** + * 获取 Token + */ +export function getToken(): string { + return localStorage.getItem(TOKEN_KEY) || '' +} + +/** + * 设置 Token + */ +export function setToken(token: string): void { + localStorage.setItem(TOKEN_KEY, token) +} + +/** + * 移除 Token + */ +export function removeToken(): void { + localStorage.removeItem(TOKEN_KEY) + localStorage.removeItem(REFRESH_TOKEN_KEY) +} + +/** + * 获取 Refresh Token + */ +export function getRefreshToken(): string { + return localStorage.getItem(REFRESH_TOKEN_KEY) || '' +} + +/** + * 设置 Refresh Token + */ +export function setRefreshToken(token: string): void { + localStorage.setItem(REFRESH_TOKEN_KEY, token) +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..286c0e9 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,86 @@ +/** + * 常量定义 + */ + +/** 资产状态 */ +export const ASSET_STATUS = { + pending: { label: '待入库', value: 'pending', type: 'info' }, + in_stock: { label: '在库', value: 'in_stock', type: 'success' }, + in_use: { label: '使用中', value: 'in_use', type: 'primary' }, + transferring: { label: '调拨中', value: 'transferring', type: 'warning' }, + maintenance: { label: '维修中', value: 'maintenance', type: 'warning' }, + pending_scrap: { label: '待报废', value: 'pending_scrap', type: 'danger' }, + scrapped: { label: '已报废', value: 'scrapped', type: 'danger' }, + lost: { label: '已丢失', value: 'lost', type: 'danger' } +} as const + +/** 分配单类型 */ +export const ALLOCATION_ORDER_TYPE = { + allocation: { label: '资产分配', value: 'allocation' }, + transfer: { label: '资产调拨', value: 'transfer' }, + recovery: { label: '资产回收', value: 'recovery' }, + maintenance: { label: '维修申请', value: 'maintenance' }, + scrap: { label: '报废申请', value: 'scrap' } +} as const + +/** 审批状态 */ +export const APPROVAL_STATUS = { + pending: { label: '待审批', value: 'pending', type: 'warning' }, + approved: { label: '已通过', value: 'approved', type: 'success' }, + rejected: { label: '已拒绝', value: 'rejected', type: 'danger' }, + cancelled: { label: '已取消', value: 'cancelled', type: 'info' } +} as const + +/** 用户状态 */ +export const USER_STATUS = { + active: { label: '正常', value: 'active', type: 'success' }, + disabled: { label: '禁用', value: 'disabled', type: 'danger' }, + locked: { label: '锁定', value: 'locked', type: 'warning' } +} as const + +/** 机构类型 */ +export const ORG_TYPE = { + province: { label: '省级', value: 'province' }, + city: { label: '市级', value: 'city' }, + outlet: { label: '网点', value: 'outlet' } +} as const + +/** 维修优先级 */ +export const MAINTENANCE_PRIORITY = { + low: { label: '低', value: 'low', type: 'info' }, + medium: { label: '中', value: 'medium', type: 'warning' }, + high: { label: '高', value: 'high', type: 'danger' } +} as const + +/** 维修类型 */ +export const MAINTENANCE_TYPE = { + self_repair: { label: '自行维修', value: 'self_repair' }, + vendor_repair: { label: '厂商维修', value: 'vendor_repair' } +} as const + +/** 故障类型 */ +export const FAULT_TYPE = { + hardware: { label: '硬件故障', value: 'hardware' }, + software: { label: '软件故障', value: 'software' }, + other: { label: '其他', value: 'other' } +} as const + +/** 字段类型 */ +export const FIELD_TYPE = { + text: { label: '单行文本', value: 'text' }, + textarea: { label: '多行文本', value: 'textarea' }, + number: { label: '数字', value: 'number' }, + date: { label: '日期', value: 'date' }, + select: { label: '下拉选择', value: 'select' }, + checkbox: { label: '复选框', value: 'checkbox' }, + url: { label: '链接', value: 'url' }, + email: { label: '邮箱', value: 'email' }, + phone: { label: '手机号', value: 'phone' } +} as const + +/** 分页大小选项 */ +export const PAGE_SIZES = [10, 20, 50, 100] + +/** 日期格式 */ +export const DATE_FORMAT = 'YYYY-MM-DD' +export const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' diff --git a/src/utils/echarts.ts b/src/utils/echarts.ts new file mode 100644 index 0000000..caff02a --- /dev/null +++ b/src/utils/echarts.ts @@ -0,0 +1,500 @@ +/** + * ECharts 工具函数和配置 + * + * 提供统一的 ECharts 主题、配置和工具函数 + */ + +import type { EChartOption } from 'echarts' + +/** + * ECharts 主题配置 - 青灰色系 + */ +export const echartsTheme = { + // 颜色系列 + color: [ + '#475569', // 主色(青灰) + '#64748b', // 次要色 + '#94a3b8', // 辅助色 + '#cbd5e1', // 浅灰色 + '#f59e0b', // 强调色(橙黄) + '#10b981', // 成功色(绿) + '#ef4444', // 危险色(红) + '#3b82f6', // 信息色(蓝) + ], + + // 背景色 + bgColor: '#ffffff', + + // 文字颜色 + textColor: '#1e293b', + textColor2: '#64748b', + + // 边框颜色 + borderColor: '#e2e8f0', + borderColor2: '#cbd5e1', + + // 分隔线颜色 + splitLineColor: '#f1f5f9', + + // 图表内边距 + padding: [20, 20, 20, 20], + + // 字体 + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', +} + +/** + * 资产状态颜色映射 + */ +export const assetStatusColors: Record = { + pending: '#94a3b8', // 待入账 - 灰色 + in_stock: '#3b82f6', // 库存中 - 蓝色 + in_use: '#10b981', // 在用 - 绿色 + transferring: '#f59e0b', // 调拨中 - 橙色 + maintenance: '#ef4444', // 维修中 - 红色 + pending_scrap: '#8b5cf6', // 待报废 - 紫色 + scrapped: '#64748b', // 已报废 - 深灰 + lost: '#dc2626', // 丢失 - 深红 +} + +/** + * 资产状态名称映射 + */ +export const assetStatusNames: Record = { + pending: '待入账', + in_stock: '库存中', + in_use: '在用', + transferring: '调拨中', + maintenance: '维修中', + pending_scrap: '待报废', + scrapped: '已报废', + lost: '已丢失', +} + +/** + * 基础图表配置 + */ +export const baseChartOption: EChartOption = { + backgroundColor: echartsTheme.bgColor, + textStyle: { + fontFamily: echartsTheme.fontFamily, + color: echartsTheme.textColor, + }, + title: { + textStyle: { + color: echartsTheme.textColor, + fontSize: 16, + fontWeight: 600, + }, + subtextStyle: { + color: echartsTheme.textColor2, + fontSize: 12, + }, + left: 'center', + top: 10, + }, + tooltip: { + trigger: 'axis', + backgroundColor: 'rgba(255, 255, 255, 0.95)', + borderColor: echartsTheme.borderColor, + borderWidth: 1, + textStyle: { + color: echartsTheme.textColor, + fontSize: 12, + }, + extraCssText: 'box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 4px;', + }, + legend: { + textStyle: { + color: echartsTheme.textColor, + fontSize: 12, + }, + top: 40, + left: 'center', + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + top: 80, + containLabel: true, + }, +} + +/** + * 饼图配置 + */ +export const pieChartOption: EChartOption = { + ...baseChartOption, + tooltip: { + ...baseChartOption.tooltip, + trigger: 'item', + formatter: '{b}: {c} ({d}%)', + }, + legend: { + ...baseChartOption.legend, + orient: 'horizontal', + itemGap: 20, + }, + series: [ + { + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: true, + itemStyle: { + borderRadius: 8, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: true, + formatter: '{b}: {d}%', + color: echartsTheme.textColor, + fontSize: 12, + }, + emphasis: { + label: { + show: true, + fontSize: 14, + fontWeight: 'bold', + }, + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.2)', + }, + }, + labelLine: { + show: true, + length: 15, + length2: 10, + smooth: true, + }, + }, + ], +} + +/** + * 柱状图配置 + */ +export const barChartOption: EChartOption = { + ...baseChartOption, + tooltip: { + ...baseChartOption.tooltip, + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'category', + axisLine: { + lineStyle: { + color: echartsTheme.borderColor, + }, + }, + axisTick: { + alignWithLabel: true, + }, + axisLabel: { + color: echartsTheme.textColor2, + fontSize: 12, + interval: 0, + rotate: 0, + }, + }, + yAxis: { + type: 'value', + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + color: echartsTheme.textColor2, + fontSize: 12, + }, + splitLine: { + lineStyle: { + color: echartsTheme.splitLineColor, + type: 'dashed', + }, + }, + }, + series: [ + { + type: 'bar', + barMaxWidth: 50, + itemStyle: { + borderRadius: [4, 4, 0, 0], + }, + }, + ], +} + +/** + * 折线图配置 + */ +export const lineChartOption: EChartOption = { + ...baseChartOption, + tooltip: { + ...baseChartOption.tooltip, + trigger: 'axis', + }, + xAxis: { + type: 'category', + boundaryGap: false, + axisLine: { + lineStyle: { + color: echartsTheme.borderColor, + }, + }, + axisLabel: { + color: echartsTheme.textColor2, + fontSize: 12, + }, + }, + yAxis: { + type: 'value', + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + color: echartsTheme.textColor2, + fontSize: 12, + }, + splitLine: { + lineStyle: { + color: echartsTheme.splitLineColor, + type: 'dashed', + }, + }, + }, + series: [ + { + type: 'line', + smooth: true, + symbol: 'circle', + symbolSize: 6, + lineStyle: { + width: 2, + }, + areaStyle: { + opacity: 0.1, + }, + }, + ], +} + +/** + * 仪表盘配置 + */ +export const gaugeChartOption: EChartOption = { + ...baseChartOption, + series: [ + { + type: 'gauge', + startAngle: 180, + endAngle: 0, + min: 0, + max: 100, + splitNumber: 10, + radius: '80%', + center: ['50%', '60%'], + itemStyle: { + color: echartsTheme.color[4], + }, + progress: { + show: true, + roundCap: true, + width: 18, + }, + pointer: { + show: false, + }, + axisLine: { + roundCap: true, + lineStyle: { + width: 18, + color: [[1, echartsTheme.borderColor2]], + }, + }, + axisTick: { + splitNumber: 5, + lineStyle: { + width: 2, + color: '#999', + }, + }, + splitLine: { + length: 12, + lineStyle: { + width: 3, + color: '#999', + }, + }, + axisLabel: { + distance: 25, + color: echartsTheme.textColor2, + fontSize: 12, + }, + detail: { + backgroundColor: echartsTheme.color[0], + borderColor: echartsTheme.color[0], + width: '60%', + lineHeight: 40, + height: 40, + borderRadius: 20, + offsetCenter: [0, '15%'], + valueAnimation: true, + formatter: '{value}%', + textStyle: { + fontSize: 24, + fontWeight: 'bold', + color: '#fff', + }, + }, + data: [ + { + value: 0, + }, + ], + }, + ], +} + +/** + * 漏斗图配置 + */ +export const funnelChartOption: EChartOption = { + ...baseChartOption, + tooltip: { + ...baseChartOption.tooltip, + trigger: 'item', + formatter: '{b}: {c}', + }, + series: [ + { + type: 'funnel', + left: '10%', + top: 60, + bottom: 60, + width: '80%', + minSize: '0%', + maxSize: '100%', + sort: 'descending', + gap: 2, + label: { + show: true, + position: 'inside', + formatter: '{b}: {c}', + fontSize: 12, + color: '#fff', + }, + labelLine: { + length: 10, + lineStyle: { + width: 1, + type: 'solid', + }, + }, + itemStyle: { + borderColor: '#fff', + borderWidth: 1, + }, + emphasis: { + label: { + fontSize: 14, + }, + }, + }, + ], +} + +/** + * 格式化数值 + * @param value 数值 + * @param decimals 小数位数 + */ +export function formatNumber(value: number, decimals: number = 2): string { + if (value === 0) return '0' + if (value < 1000) return value.toFixed(decimals) + if (value < 10000) return (value / 1000).toFixed(decimals) + 'K' + if (value < 1000000) return (value / 10000).toFixed(decimals) + '万' + return (value / 1000000).toFixed(decimals) + 'M' +} + +/** + * 格式化金额 + * @param value 金额 + */ +export function formatCurrency(value: number): string { + if (value === 0) return '¥0.00' + if (value < 10000) return `¥${value.toFixed(2)}` + if (value < 100000000) return `¥${(value / 10000).toFixed(2)}万` + return `¥${(value / 100000000).toFixed(2)}亿` +} + +/** + * 格式化百分比 + * @param value 数值 + * @param total 总数 + */ +export function formatPercentage(value: number, total: number): string { + if (total === 0) return '0%' + return ((value / total) * 100).toFixed(1) + '%' +} + +/** + * 生成图表颜色 + * @param index 索引 + */ +export function getColor(index: number): string { + return echartsTheme.color[index % echartsTheme.color.length] +} + +/** + * 获取资产状态颜色 + * @param status 资产状态 + */ +export function getAssetStatusColor(status: string): string { + return assetStatusColors[status] || echartsTheme.color[0] +} + +/** + * 获取资产状态名称 + * @param status 资产状态 + */ +export function getAssetStatusName(status: string): string { + return assetStatusNames[status] || status +} + +/** + * 适配图表尺寸 + * @param chartRef 图表容器引用 + * @param delay 延迟时间(ms) + */ +export function resizeChart(chartRef: any, delay: number = 300) { + setTimeout(() => { + if (chartRef && chartRef.resize) { + chartRef.resize() + } + }, delay) +} + +/** + * 合并图表配置 + * @param target 目标配置 + * @param source 源配置 + */ +export function mergeOption(target: any, source: any): EChartOption { + return { + ...target, + ...source, + series: source.series || target.series, + } +} diff --git a/src/utils/echarts/performance.ts b/src/utils/echarts/performance.ts new file mode 100644 index 0000000..59f2c37 --- /dev/null +++ b/src/utils/echarts/performance.ts @@ -0,0 +1,284 @@ +/** + * ECharts 性能优化配置 + * + * 提供大数据量场景下的性能优化方案 + */ + +import type { EChartOption } from 'echarts' + +/** + * 大数据量配置 + */ +export const performanceConfig = { + // 渐进式渲染 + progressive: 1000, // 渐进式渲染阈值 + progressiveThreshold: 5000, // 开启渐进式渲染的数据量阈值 + + // 悬停层阈值 + hoverLayerThreshold: 3000, // 开启悬停层的数据量阈值 + + // 使用 UTC 时间 + useUTC: false, + + // 动画配置 + animation: true, + animationDuration: 1000, + animationDurationUpdate: 500, + animationEasing: 'cubicOut', + animationEasingUpdate: 'cubicInOut', + + // 数据量限制 + maxDataPoints: 10000, // 最大数据点数 + maxCategories: 100, // 最大分类数 +} + +/** + * 应用性能优化配置 + */ +export function applyPerformanceConfig(option: EChartOption): EChartOption { + return { + ...option, + progressive: performanceConfig.progressive, + progressiveThreshold: performanceConfig.progressiveThreshold, + hoverLayerThreshold: performanceConfig.hoverLayerThreshold, + useUTC: performanceConfig.useUTC, + animation: performanceConfig.animation, + animationDuration: performanceConfig.animationDuration, + animationDurationUpdate: performanceConfig.animationDurationUpdate, + animationEasing: performanceConfig.animationEasing, + animationEasingUpdate: performanceConfig.animationEasingUpdate, + } +} + +/** + * 采样数据(大数据量场景) + */ +export function sampleData(data: T[], maxSize: number): T[] { + if (data.length <= maxSize) return data + + const step = Math.ceil(data.length / maxSize) + return data.filter((_, index) => index % step === 0) +} + +/** + * 聚合数据(时间序列) + */ +export function aggregateDataByTime( + data: Array<{ date: string; value: number }>, + interval: 'day' | 'week' | 'month' | 'year' +): Array<{ date: string; value: number }> { + const grouped = new Map() + + data.forEach((item) => { + const date = new Date(item.date) + let key: string + + switch (interval) { + case 'day': + key = date.toISOString().split('T')[0] + break + case 'week': + const weekStart = new Date(date) + weekStart.setDate(date.getDate() - date.getDay()) + key = weekStart.toISOString().split('T')[0] + break + case 'month': + key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}` + break + case 'year': + key = String(date.getFullYear()) + break + } + + if (!grouped.has(key)) { + grouped.set(key, []) + } + grouped.get(key)!.push(item.value) + }) + + return Array.from(grouped.entries()).map(([date, values]) => ({ + date, + value: values.reduce((sum, v) => sum + v, 0) / values.length, + })) +} + +/** + * 分页数据 + */ +export function paginateData(data: T[], page: number, pageSize: number): T[] { + const start = page * pageSize + const end = start + pageSize + return data.slice(start, end) +} + +/** + * 虚拟滚动配置 + */ +export const virtualScrollConfig = { + enabled: true, + itemSize: 40, // 每项高度 + bufferSize: 10, // 缓冲区大小 + threshold: 100, // 启用虚拟滚动的数据量阈值 +} + +/** + * 数据压缩配置 + */ +export const dataCompressionConfig = { + enabled: true, + threshold: 1000, // 启用压缩的数据量阈值 + algorithm: 'lttb', // 算法: lttb (Largest-Triangle-Three-Buckets) +} + +/** + * LTTB 算法实现( downsampling) + */ +export function lttbDownsampling( + data: Array<{ x: number; y: number }>, + threshold: number +): Array<{ x: number; y: number }> { + if (threshold >= data.length || threshold <= 0) return data + + const sampled = [] + const bucketSize = (data.length - 2) / (threshold - 2) + + let a = 0 // 初始点 + sampled.push(data[a]) + + let maxAreaPoint + let maxArea + let area + + for (let i = 0; i < threshold - 2; i++) { + // 计算下一个桶的边界 + const avgRangeStart = Math.floor((i + 1) * bucketSize) + 1 + const avgRangeEnd = Math.floor((i + 2) * bucketSize) + 1 + const avgRangeLength = avgRangeEnd - avgRangeStart + + const avgX = 0 + const avgY = 0 + + for (let j = avgRangeStart; j < avgRangeEnd && j < data.length; j++) { + avgX += data[j].x + avgY += data[j].y + } + + avgX /= avgRangeLength + avgY /= avgRangeLength + + // 获取桶的起始和结束点 + const rangeOffs = Math.floor((i + 0) * bucketSize) + 1 + const rangeTo = Math.floor((i + 1) * bucketSize) + 1 + + const pointAX = data[a].x + const pointAY = data[a].y + + maxArea = -1 + maxAreaPoint = data[rangeOffs] + + for (let j = rangeOffs; j < rangeTo && j < data.length; j++) { + area = Math.abs( + (pointAX - avgX) * (data[j].y - pointAY) - + (pointAX - data[j].x) * (avgY - pointAY) + ) + + if (area > maxArea) { + maxArea = area + maxAreaPoint = data[j] + } + } + + sampled.push(maxAreaPoint) + a = rangeOffs + } + + sampled.push(data[data.length - 1]) // 最后一个点 + + return sampled +} + +/** + * 防抖函数(用于 resize) + */ +export function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: ReturnType | null = null + + return function (this: any, ...args: Parameters) { + if (timeout) clearTimeout(timeout) + timeout = setTimeout(() => { + func.apply(this, args) + }, wait) + } +} + +/** + * 节流函数(用于滚动事件) + */ +export function throttle any>( + func: T, + limit: number +): (...args: Parameters) => void { + let inThrottle: boolean = false + + return function (this: any, ...args: Parameters) { + if (!inThrottle) { + func.apply(this, args) + inThrottle = true + setTimeout(() => (inThrottle = false), limit) + } + } +} + +/** + * 监控图表性能 + */ +export class ChartPerformanceMonitor { + private renderTimes: number[] = [] + private maxSamples = 100 + + recordRenderTime(time: number) { + this.renderTimes.push(time) + if (this.renderTimes.length > this.maxSamples) { + this.renderTimes.shift() + } + } + + getAverageRenderTime(): number { + if (this.renderTimes.length === 0) return 0 + const sum = this.renderTimes.reduce((a, b) => a + b, 0) + return sum / this.renderTimes.length + } + + getMaxRenderTime(): number { + if (this.renderTimes.length === 0) return 0 + return Math.max(...this.renderTimes) + } + + getMinRenderTime(): number { + if (this.renderTimes.length === 0) return 0 + return Math.min(...this.renderTimes) + } + + getStats() { + return { + average: this.getAverageRenderTime(), + max: this.getMaxRenderTime(), + min: this.getMinRenderTime(), + samples: this.renderTimes.length, + } + } + + clear() { + this.renderTimes = [] + } +} + +/** + * 创建性能监控器 + */ +export function createPerformanceMonitor(): ChartPerformanceMonitor { + return new ChartPerformanceMonitor() +} diff --git a/src/utils/fieldDependency.ts b/src/utils/fieldDependency.ts new file mode 100644 index 0000000..d69042d --- /dev/null +++ b/src/utils/fieldDependency.ts @@ -0,0 +1,283 @@ +/** + * 字段联动管理器 + * 管理字段之间的依赖关系和联动逻辑 + */ + +import type { FieldDependency, FormData } from '@/types/form' + +/** + * 字段联动管理器类 + */ +export class FieldDependencyManager { + /** 联动配置列表 */ + private dependencies: FieldDependency[] = [] + + /** 字段变化回调 */ + private callbacks: Map void>> = new Map() + + /** + * 添加联动配置 + * @param dep 联动配置 + */ + addDependency(dep: FieldDependency): void { + // 检查是否已存在相同的联动配置 + const existingIndex = this.dependencies.findIndex( + (d) => d.sourceField === dep.sourceField && d.targetField === dep.targetField + ) + + if (existingIndex >= 0) { + // 替换现有配置 + this.dependencies[existingIndex] = dep + } else { + // 添加新配置 + this.dependencies.push(dep) + } + } + + /** + * 批量添加联动配置 + * @param deps 联动配置列表 + */ + addDependencies(deps: FieldDependency[]): void { + deps.forEach((dep) => this.addDependency(dep)) + } + + /** + * 移除联动配置 + * @param sourceField 源字段 + * @param targetField 目标字段 + */ + removeDependency(sourceField: string, targetField: string): void { + this.dependencies = this.dependencies.filter( + (d) => !(d.sourceField === sourceField && d.targetField === targetField) + ) + } + + /** + * 清空所有联动配置 + */ + clear(): void { + this.dependencies = [] + this.callbacks.clear() + } + + /** + * 触发联动 + * @param sourceField 源字段名称 + * @param sourceValue 源字段值 + * @param allFormData 所有表单数据 + * @returns 联动结果 { [targetField]: { type, value } } + */ + trigger( + sourceField: string, + sourceValue: any, + allFormData: FormData + ): Record { + const results: Record = {} + + // 找到所有与源字段相关的联动配置 + const relatedDeps = this.dependencies.filter((dep) => dep.sourceField === sourceField) + + relatedDeps.forEach((dep) => { + // 检查条件是否满足 + if (dep.condition(sourceValue, allFormData)) { + let result: any = undefined + + // 执行联动动作 + switch (dep.type) { + case 'show': + case 'hide': + result = dep.type === 'show' + break + + case 'enable': + case 'disable': + result = dep.type !== 'disable' + break + + case 'setValue': + if (dep.action) { + result = dep.action( + allFormData[dep.targetField], + sourceValue, + allFormData + ) + } + break + + case 'setOptions': + if (dep.action) { + result = dep.action( + allFormData[dep.targetField], + sourceValue, + allFormData + ) + } + break + } + + results[dep.targetField] = { + type: dep.type, + value: result + } + + // 触发回调 + this.emit(sourceField, dep.targetField, { type: dep.type, value: result }) + } + }) + + return results + } + + /** + * 注册字段变化回调 + * @param sourceField 源字段 + * @param callback 回调函数 + */ + on(sourceField: string, callback: (targetField: string, action: any) => void): void { + if (!this.callbacks.has(sourceField)) { + this.callbacks.set(sourceField, new Set()) + } + this.callbacks.get(sourceField)!.add(callback) + } + + /** + * 取消注册回调 + * @param sourceField 源字段 + * @param callback 回调函数 + */ + off(sourceField: string, callback: (targetField: string, action: any) => void): void { + const callbacks = this.callbacks.get(sourceField) + if (callbacks) { + callbacks.delete(callback) + } + } + + /** + * 触发回调 + */ + private emit(sourceField: string, targetField: string, action: any): void { + const callbacks = this.callbacks.get(sourceField) + if (callbacks) { + callbacks.forEach((cb) => cb(targetField, action)) + } + } + + /** + * 获取所有联动配置 + */ + getDependencies(): FieldDependency[] { + return [...this.dependencies] + } + + /** + * 获取与指定字段相关的所有联动 + * @param fieldName 字段名称 + */ + getFieldDependencies(fieldName: string): { + asSource: FieldDependency[] + asTarget: FieldDependency[] + } { + return { + asSource: this.dependencies.filter((dep) => dep.sourceField === fieldName), + asTarget: this.dependencies.filter((dep) => dep.targetField === fieldName) + } + } +} + +/** + * 创建常用的联动条件函数 + */ +export const DependencyConditions = { + /** + * 等于某个值 + */ + equals: (value: any) => (sourceValue: any) => sourceValue === value, + + /** + * 不等于某个值 + */ + notEquals: (value: any) => (sourceValue: any) => sourceValue !== value, + + /** + * 包含某个值(用于数组) + */ + contains: (value: any) => (sourceValue: any) => { + if (Array.isArray(sourceValue)) { + return sourceValue.includes(value) + } + return sourceValue === value + }, + + /** + * 不包含某个值 + */ + notContains: (value: any) => (sourceValue: any) => { + if (Array.isArray(sourceValue)) { + return !sourceValue.includes(value) + } + return sourceValue !== value + }, + + /** + * 大于某个值 + */ + greaterThan: (value: any) => (sourceValue: any) => Number(sourceValue) > Number(value), + + /** + * 小于某个值 + */ + lessThan: (value: any) => (sourceValue: any) => Number(sourceValue) < Number(value), + + /** + * 在某个范围内 + */ + between: (min: number, max: number) => (sourceValue: any) => { + const num = Number(sourceValue) + return num >= min && num <= max + }, + + /** + * 值为真 + */ + isTrue: () => (sourceValue: any) => Boolean(sourceValue), + + /** + * 值为假 + */ + isFalse: () => (sourceValue: any) => !Boolean(sourceValue) +} + +/** + * 创建常用的联动动作函数 + */ +export const DependencyActions = { + /** + * 设置为固定值 + */ + setValue: (value: any) => () => value, + + /** + * 清空值 + */ + clearValue: () => () => undefined, + + /** + * 根据源值设置目标值 + */ + copyValue: () => (_target: any, source: any) => source, + + /** + * 动态加载选项 + */ + loadOptions: (optionsLoader: (sourceValue: any) => Array<{ label: string; value: any }>) => { + return () => async (_target: any, source: any, allData: FormData) => { + return await optionsLoader(source) + } + } +} + +/** + * 导出单例实例 + */ +export const fieldDependencyManager = new FieldDependencyManager() diff --git a/src/utils/fieldValidator.ts b/src/utils/fieldValidator.ts new file mode 100644 index 0000000..a74df17 --- /dev/null +++ b/src/utils/fieldValidator.ts @@ -0,0 +1,261 @@ +/** + * 动态字段验证器 + * 根据字段配置进行表单验证 + */ + +import type { FieldConfig, ValidationResult, FormData } from '@/types/form' + +/** + * 验证单个字段 + * @param value 字段值 + * @param field 字段配置 + * @param allFormData 所有表单数据(用于自定义验证) + * @returns 验证结果 + */ +export function validateField( + value: any, + field: FieldConfig, + allFormData: FormData = {} +): ValidationResult { + const errors: string[] = [] + + // 1. 必填验证 + if (field.required) { + if (value === undefined || value === null || value === '') { + errors.push(`${field.label}不能为空`) + return { isValid: false, errors } + } + } + + // 如果值为空且非必填,则跳过后续验证 + if (!field.required && (value === undefined || value === null || value === '')) { + return { isValid: true, errors: [] } + } + + // 2. 根据字段类型进行验证 + switch (field.fieldType) { + case 'text': + case 'textarea': + validateText(value, field, errors) + break + case 'number': + validateNumber(value, field, errors) + break + case 'email': + validateEmail(value, field, errors) + break + case 'phone': + validatePhone(value, field, errors) + break + case 'url': + validateUrl(value, field, errors) + break + case 'select': + case 'multiselect': + case 'boolean': + case 'date': + case 'tree': + // 这些类型一般不需要额外验证 + break + } + + // 3. 自定义正则验证 + if (field.validationRules?.pattern && value) { + try { + const regex = new RegExp(field.validationRules.pattern) + if (!regex.test(value)) { + errors.push(field.validationRules.customMessage || `${field.label}格式不正确`) + } + } catch (error) { + console.error('正则表达式验证失败:', error) + } + } + + // 4. 自定义验证函数 + if (field.validationRules?.custom) { + try { + const result = field.validationRules.custom(value, allFormData) + if (result !== true) { + errors.push(typeof result === 'string' ? result : `${field.label}验证失败`) + } + } catch (error) { + console.error('自定义验证失败:', error) + errors.push(`${field.label}验证出错`) + } + } + + return { + isValid: errors.length === 0, + errors + } +} + +/** + * 验证所有字段 + * @param data 表单数据 + * @param fields 字段配置列表 + * @returns 字段级错误信息 + */ +export function validateFields( + data: FormData, + fields: FieldConfig[] +): Record { + const errors: Record = {} + + fields.forEach((field) => { + const value = data[field.name] + const result = validateField(value, field, data) + + if (!result.isValid) { + errors[field.name] = result.errors + } + }) + + return errors +} + +/** + * 文本类型验证 + */ +function validateText(value: any, field: FieldConfig, errors: string[]): void { + if (typeof value !== 'string') { + errors.push(`${field.label}必须是文本`) + return + } + + const { min, max } = field.validationRules || {} + + if (min && value.length < min) { + errors.push(`${field.label}长度不能少于${min}个字符`) + } + + if (max && value.length > max) { + errors.push(`${field.label}长度不能超过${max}个字符`) + } +} + +/** + * 数字类型验证 + */ +function validateNumber(value: any, field: FieldConfig, errors: string[]): void { + const num = Number(value) + + if (isNaN(num)) { + errors.push(`${field.label}必须是数字`) + return + } + + const { min, max } = field.validationRules || {} + + if (min !== undefined && num < min) { + errors.push(`${field.label}不能小于${min}`) + } + + if (max !== undefined && num > max) { + errors.push(`${field.label}不能大于${max}`) + } +} + +/** + * 邮箱验证 + */ +function validateEmail(value: any, field: FieldConfig, errors: string[]): void { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + if (!emailRegex.test(value)) { + errors.push(`${field.label}邮箱格式不正确`) + } +} + +/** + * 手机号验证 + */ +function validatePhone(value: any, field: FieldConfig, errors: string[]): void { + // 中国大陆手机号验证 + const phoneRegex = /^1[3-9]\d{9}$/ + if (!phoneRegex.test(value)) { + errors.push(`${field.label}手机号格式不正确`) + } +} + +/** + * URL验证 + */ +function validateUrl(value: any, field: FieldConfig, errors: string[]): void { + try { + new URL(value) + } catch { + errors.push(`${field.label}URL格式不正确`) + } +} + +/** + * 创建VeeValidate验证规则 + * @param field 字段配置 + * @returns VeeValidate规则对象 + */ +export function createValidationRule(field: FieldConfig): any { + const rules: any = {} + + // 必填规则 + if (field.required) { + rules.required = true + } + + // 根据字段类型添加规则 + switch (field.fieldType) { + case 'text': + case 'textarea': + if (field.validationRules?.min) { + rules.min = field.validationRules.min + } + if (field.validationRules?.max) { + rules.max = field.validationRules.max + } + if (field.validationRules?.pattern) { + rules.regex = new RegExp(field.validationRules.pattern) + } + break + + case 'number': + if (field.validationRules?.min !== undefined) { + rules.min_value = field.validationRules.min + } + if (field.validationRules?.max !== undefined) { + rules.max_value = field.validationRules.max + } + break + + case 'email': + rules.email = true + break + + case 'phone': + rules.regex = /^1[3-9]\d{9}$/ + break + + case 'url': + rules.url = true + break + } + + return rules +} + +/** + * 获取字段错误消息 + * @param field 字段配置 + * @param errorType 错误类型 + * @returns 错误消息 + */ +export function getFieldErrorMessage(field: FieldConfig, errorType: string): string { + const messages: Record = { + required: `${field.label}不能为空`, + min: `${field.label}长度/值不能小于${field.validationRules?.min}`, + max: `${field.label}长度/值不能大于${field.validationRules?.max}`, + email: `${field.label}邮箱格式不正确`, + url: `${field.label}URL格式不正确`, + regex: `${field.label}格式不正确` + } + + return messages[errorType] || `${field.label}验证失败` +} diff --git a/src/utils/file.ts b/src/utils/file.ts new file mode 100644 index 0000000..9c33945 --- /dev/null +++ b/src/utils/file.ts @@ -0,0 +1,425 @@ +/** + * 文件工具函数 + */ + +/** + * 格式化文件大小 + * @param bytes 文件大小(字节) + * @returns 格式化后的文件大小字符串 + */ +export function formatFileSize(bytes: number): string { + if (bytes === 0) return '0 B' + + const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] + const k = 1024 + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}` +} + +/** + * 格式化日期时间 + * @param dateString 日期字符串 + * @param format 格式化模板,默认 'YYYY-MM-DD HH:mm:ss' + * @returns 格式化后的日期时间字符串 + */ +export function formatDateTime(dateString: string, format: string = 'YYYY-MM-DD HH:mm:ss'): string { + if (!dateString) return '' + + const date = new Date(dateString) + + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + const hours = String(date.getHours()).padStart(2, '0') + const minutes = String(date.getMinutes()).padStart(2, '0') + const seconds = String(date.getSeconds()).padStart(2, '0') + + return format + .replace('YYYY', String(year)) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hours) + .replace('mm', minutes) + .replace('ss', seconds) +} + +/** + * 获取文件扩展名 + * @param filename 文件名 + * @returns 文件扩展名(包含点号) + */ +export function getFileExtension(filename: string): string { + const lastDotIndex = filename.lastIndexOf('.') + return lastDotIndex > -1 ? filename.substring(lastDotIndex) : '' +} + +/** + * 获取文件名(不含扩展名) + * @param filename 文件名 + * @returns 不含扩展名的文件名 + */ +export function getFileNameWithoutExtension(filename: string): string { + const lastDotIndex = filename.lastIndexOf('.') + return lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename +} + +/** + * 判断是否为图片文件 + * @param mimeType MIME类型 + * @returns 是否为图片 + */ +export function isImage(mimeType: string): boolean { + return mimeType?.startsWith('image/') || false +} + +/** + * 判断是否为PDF文件 + * @param mimeType MIME类型 + * @returns 是否为PDF + */ +export function isPDF(mimeType: string): boolean { + return mimeType === 'application/pdf' +} + +/** + * 判断是否为文档文件 + * @param mimeType MIME类型 + * @returns 是否为文档 + */ +export function isDocument(mimeType: string): boolean { + const documentTypes = [ + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'text/plain', + 'text/csv' + ] + return documentTypes.includes(mimeType) +} + +/** + * 判断是否为压缩包文件 + * @param mimeType MIME类型 + * @returns 是否为压缩包 + */ +export function isArchive(mimeType: string): boolean { + const archiveTypes = [ + 'application/zip', + 'application/x-rar-compressed', + 'application/x-7z-compressed' + ] + return archiveTypes.includes(mimeType) +} + +/** + * 获取文件类型图标 + * @param mimeType MIME类型 + * @returns 图标名称 + */ +export function getFileTypeIcon(mimeType: string): string { + if (isImage(mimeType)) return 'picture' + if (isPDF(mimeType)) return 'document' + if (isDocument(mimeType)) return 'document' + if (isArchive(mimeType)) return 'folder' + return 'files' +} + +/** + * 下载文件 + * @param url 文件URL + * @param filename 文件名 + * @returns Promise + */ +export async function downloadFile(url: string, filename?: string): Promise { + try { + const response = await fetch(url) + if (!response.ok) { + throw new Error('下载失败') + } + + const blob = await response.blob() + const blobUrl = window.URL.createObjectURL(blob) + + const link = document.createElement('a') + link.href = blobUrl + link.download = filename || getFilenameFromUrl(url) + + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + + window.URL.revokeObjectURL(blobUrl) + } catch (error) { + console.error('下载文件失败:', error) + throw error + } +} + +/** + * 从URL中提取文件名 + * @param url URL地址 + * @returns 文件名 + */ +function getFilenameFromUrl(url: string): string { + try { + const urlObj = new URL(url) + const pathname = urlObj.pathname + const parts = pathname.split('/') + return parts[parts.length - 1] || 'download' + } catch { + return 'download' + } +} + +/** + * 预览文件(新窗口打开) + * @param url 文件URL + */ +export function previewFile(url: string): void { + window.open(url, '_blank') +} + +/** + * 复制文件到剪贴板 + * @param file File对象 + * @returns Promise + */ +export async function copyFileToClipboard(file: File): Promise { + try { + await navigator.clipboard.write([ + new ClipboardItem({ + [file.type]: file + }) + ]) + return true + } catch (error) { + console.error('复制文件失败:', error) + return false + } +} + +/** + * 读取文件为DataURL + * @param file File对象 + * @returns Promise + */ +export function readFileAsDataURL(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = () => resolve(reader.result as string) + reader.onerror = reject + reader.readAsDataURL(file) + }) +} + +/** + * 读取文件为文本 + * @param file File对象 + * @returns Promise + */ +export function readFileAsText(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = () => resolve(reader.result as string) + reader.onerror = reject + reader.readAsText(file) + }) +} + +/** + * 计算文件哈希值(简单实现) + * @param file File对象 + * @returns Promise + */ +export async function calculateFileHash(file: File): Promise { + const buffer = await file.arrayBuffer() + const hashBuffer = await crypto.subtle.digest('SHA-256', buffer) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + return hashHex +} + +/** + * 压缩图片 + * @param file 图片文件 + * @param quality 压缩质量(0-1) + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @returns Promise + */ +export async function compressImage( + file: File, + quality: number = 0.8, + maxWidth: number = 1920, + maxHeight: number = 1080 +): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = (e) => { + const img = new Image() + img.onload = () => { + const canvas = document.createElement('canvas') + let width = img.width + let height = img.height + + // 计算缩放比例 + if (width > maxWidth || height > maxHeight) { + const ratio = Math.min(maxWidth / width, maxHeight / height) + width = width * ratio + height = height * ratio + } + + canvas.width = width + canvas.height = height + + const ctx = canvas.getContext('2d') + if (ctx) { + ctx.drawImage(img, 0, 0, width, height) + canvas.toBlob( + (blob) => { + if (blob) { + resolve(blob) + } else { + reject(new Error('压缩失败')) + } + }, + file.type, + quality + ) + } else { + reject(new Error('无法创建canvas上下文')) + } + } + img.onerror = reject + img.src = e.target?.result as string + } + reader.onerror = reject + reader.readAsDataURL(file) + }) +} + +/** + * 验证文件类型 + * @param file File对象 + * @param allowedTypes 允许的类型列表 + * @returns 是否通过验证 + */ +export function validateFileType(file: File, allowedTypes: string[]): boolean { + return allowedTypes.some(type => { + if (type.startsWith('.')) { + return file.name.toLowerCase().endsWith(type.toLowerCase()) + } + return file.type.includes(type) + }) +} + +/** + * 验证文件大小 + * @param file File对象 + * @param maxSize 最大大小(字节) + * @returns 是否通过验证 + */ +export function validateFileSize(file: File, maxSize: number): boolean { + return file.size <= maxSize +} + +/** + * 批量验证文件 + * @param files 文件列表 + * @param options 验证选项 + * @returns 验证结果 + */ +export function validateFiles( + files: File[], + options: { + allowedTypes?: string[] + maxSize?: number + maxCount?: number + } +): { valid: boolean; errors: string[] } { + const errors: string[] = [] + + // 检查文件数量 + if (options.maxCount && files.length > options.maxCount) { + errors.push(`最多只能上传 ${options.maxCount} 个文件`) + return { valid: false, errors } + } + + // 检查每个文件 + files.forEach((file, index) => { + // 检查类型 + if (options.allowedTypes && !validateFileType(file, options.allowedTypes)) { + errors.push(`文件 "${file.name}" 的类型不支持`) + } + + // 检查大小 + if (options.maxSize && !validateFileSize(file, options.maxSize)) { + const maxSizeMB = (options.maxSize / 1024 / 1024).toFixed(0) + errors.push(`文件 "${file.name}" 大小超过 ${maxSizeMB}MB`) + } + }) + + return { + valid: errors.length === 0, + errors + } +} + +/** + * 生成唯一文件名 + * @param originalFilename 原始文件名 + * @returns 唯一文件名 + */ +export function generateUniqueFilename(originalFilename: string): string { + const ext = getFileExtension(originalFilename) + const name = getFileNameWithoutExtension(originalFilename) + const timestamp = Date.now() + const random = Math.random().toString(36).substring(2, 8) + return `${name}_${timestamp}_${random}${ext}` +} + +/** + * 创建缩略图 + * @param file 图片文件 + * @param width 缩略图宽度 + * @param height 缩略图高度 + * @returns Promise DataURL + */ +export async function createThumbnail( + file: File, + width: number = 200, + height: number = 200 +): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = (e) => { + const img = new Image() + img.onload = () => { + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + + const ctx = canvas.getContext('2d') + if (ctx) { + // 计算缩放比例,保持宽高比 + const scale = Math.min(width / img.width, height / img.height) + const x = (width - img.width * scale) / 2 + const y = (height - img.height * scale) / 2 + + ctx.drawImage(img, 0, 0, img.width, img.height, x, y, img.width * scale, img.height * scale) + resolve(canvas.toDataURL(file.type, 0.8)) + } else { + reject(new Error('无法创建canvas上下文')) + } + } + img.onerror = reject + img.src = e.target?.result as string + } + reader.onerror = reject + reader.readAsDataURL(file) + }) +} diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..649eaf7 --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,47 @@ +/** + * 格式化工具函数 + */ +import dayjs from 'dayjs' + +/** + * 格式化日期时间 + */ +export function formatDateTime(date: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string { + if (!date) return '-' + return dayjs(date).format(format) +} + +/** + * 格式化日期 + */ +export function formatDate(date: string | Date): string { + if (!date) return '-' + return dayjs(date).format('YYYY-MM-DD') +} + +/** + * 格式化金额 + */ +export function formatMoney(amount: number | string): string { + if (amount === null || amount === undefined) return '-' + return `¥${Number(amount).toFixed(2)}` +} + +/** + * 格式化文件大小 + */ +export function formatFileSize(bytes: number): string { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}` +} + +/** + * 格式化百分比 + */ +export function formatPercent(value: number, total: number): string { + if (total === 0) return '0%' + return `${((value / total) * 100).toFixed(1)}%` +} diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..7df57fd --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,51 @@ +/** + * 验证工具函数 + */ + +/** + * 验证邮箱 + */ +export function isEmail(email: string): boolean { + const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + return reg.test(email) +} + +/** + * 验证手机号 + */ +export function isPhone(phone: string): boolean { + const reg = /^1[3-9]\d{9}$/ + return reg.test(phone) +} + +/** + * 验证用户名(4-50字符,字母数字下划线) + */ +export function isUsername(username: string): boolean { + const reg = /^[a-zA-Z0-9_]{4,50}$/ + return reg.test(username) +} + +/** + * 验证密码(至少8位,包含大小写字母和数字) + */ +export function isPassword(password: string): boolean { + const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/ + return reg.test(password) +} + +/** + * 验证URL + */ +export function isUrl(url: string): boolean { + const reg = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/ + return reg.test(url) +} + +/** + * 验证身份证号 + */ +export function isIdCard(idCard: string): boolean { + const reg = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/ + return reg.test(idCard) +} diff --git a/src/views/FileManager.vue b/src/views/FileManager.vue new file mode 100644 index 0000000..5bb6fbb --- /dev/null +++ b/src/views/FileManager.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/views/admin/DeviceTypeManagement.vue b/src/views/admin/DeviceTypeManagement.vue new file mode 100644 index 0000000..a9a582e --- /dev/null +++ b/src/views/admin/DeviceTypeManagement.vue @@ -0,0 +1,683 @@ + + + + + diff --git a/src/views/admin/OrganizationManagement.vue b/src/views/admin/OrganizationManagement.vue new file mode 100644 index 0000000..fe1a8d0 --- /dev/null +++ b/src/views/admin/OrganizationManagement.vue @@ -0,0 +1,492 @@ + + + + + diff --git a/src/views/admin/RoleManagement.vue b/src/views/admin/RoleManagement.vue new file mode 100644 index 0000000..a674844 --- /dev/null +++ b/src/views/admin/RoleManagement.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/src/views/admin/UserManagement.vue b/src/views/admin/UserManagement.vue new file mode 100644 index 0000000..72333f1 --- /dev/null +++ b/src/views/admin/UserManagement.vue @@ -0,0 +1,548 @@ + + + + + diff --git a/src/views/allocation/AllocationList.vue b/src/views/allocation/AllocationList.vue new file mode 100644 index 0000000..8ab2c0d --- /dev/null +++ b/src/views/allocation/AllocationList.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/src/views/allocation/RecoveryList.vue b/src/views/allocation/RecoveryList.vue new file mode 100644 index 0000000..4a12451 --- /dev/null +++ b/src/views/allocation/RecoveryList.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/src/views/allocation/TransferList.vue b/src/views/allocation/TransferList.vue new file mode 100644 index 0000000..06fb0b1 --- /dev/null +++ b/src/views/allocation/TransferList.vue @@ -0,0 +1,414 @@ + + + + + diff --git a/src/views/allocation/components/AllocationDetailDialog.vue b/src/views/allocation/components/AllocationDetailDialog.vue new file mode 100644 index 0000000..565311f --- /dev/null +++ b/src/views/allocation/components/AllocationDetailDialog.vue @@ -0,0 +1,385 @@ + + + + + diff --git a/src/views/allocation/components/AssetSelectorDialog.vue b/src/views/allocation/components/AssetSelectorDialog.vue new file mode 100644 index 0000000..63ec198 --- /dev/null +++ b/src/views/allocation/components/AssetSelectorDialog.vue @@ -0,0 +1,309 @@ + + + + + + + diff --git a/src/views/allocation/components/CreateAllocationDialog.vue b/src/views/allocation/components/CreateAllocationDialog.vue new file mode 100644 index 0000000..3295a1a --- /dev/null +++ b/src/views/allocation/components/CreateAllocationDialog.vue @@ -0,0 +1,343 @@ + + + + + diff --git a/src/views/allocation/components/CreateRecoveryDialog.vue b/src/views/allocation/components/CreateRecoveryDialog.vue new file mode 100644 index 0000000..ce2f024 --- /dev/null +++ b/src/views/allocation/components/CreateRecoveryDialog.vue @@ -0,0 +1,349 @@ + + + + + diff --git a/src/views/allocation/components/CreateTransferDialog.vue b/src/views/allocation/components/CreateTransferDialog.vue new file mode 100644 index 0000000..ce58065 --- /dev/null +++ b/src/views/allocation/components/CreateTransferDialog.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/src/views/allocation/components/RecoveryDetailDialog.vue b/src/views/allocation/components/RecoveryDetailDialog.vue new file mode 100644 index 0000000..351a4b2 --- /dev/null +++ b/src/views/allocation/components/RecoveryDetailDialog.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/views/allocation/components/TransferDetailDialog.vue b/src/views/allocation/components/TransferDetailDialog.vue new file mode 100644 index 0000000..1e626fe --- /dev/null +++ b/src/views/allocation/components/TransferDetailDialog.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/views/assets/AssetAllocation.vue b/src/views/assets/AssetAllocation.vue new file mode 100644 index 0000000..9fa631e --- /dev/null +++ b/src/views/assets/AssetAllocation.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/views/assets/AssetCreate.vue b/src/views/assets/AssetCreate.vue new file mode 100644 index 0000000..829a066 --- /dev/null +++ b/src/views/assets/AssetCreate.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/src/views/assets/AssetList.vue b/src/views/assets/AssetList.vue new file mode 100644 index 0000000..8e5c760 --- /dev/null +++ b/src/views/assets/AssetList.vue @@ -0,0 +1,385 @@ + + + + + diff --git a/src/views/assets/AssetScan.vue b/src/views/assets/AssetScan.vue new file mode 100644 index 0000000..9d8e245 --- /dev/null +++ b/src/views/assets/AssetScan.vue @@ -0,0 +1,557 @@ + + + + + diff --git a/src/views/assets/MaintenanceManagement.vue b/src/views/assets/MaintenanceManagement.vue new file mode 100644 index 0000000..14f12b4 --- /dev/null +++ b/src/views/assets/MaintenanceManagement.vue @@ -0,0 +1,349 @@ + + + + + diff --git a/src/views/assets/StatisticsDashboard.vue b/src/views/assets/StatisticsDashboard.vue new file mode 100644 index 0000000..ee0e4f5 --- /dev/null +++ b/src/views/assets/StatisticsDashboard.vue @@ -0,0 +1,556 @@ + + + + + diff --git a/src/views/assets/components/AssetDetailDialog.vue b/src/views/assets/components/AssetDetailDialog.vue new file mode 100644 index 0000000..dc103a1 --- /dev/null +++ b/src/views/assets/components/AssetDetailDialog.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/src/views/assets/components/AssetEditDialog.vue b/src/views/assets/components/AssetEditDialog.vue new file mode 100644 index 0000000..8ac7774 --- /dev/null +++ b/src/views/assets/components/AssetEditDialog.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/views/assets/components/BatchExportDialog.vue b/src/views/assets/components/BatchExportDialog.vue new file mode 100644 index 0000000..fbf97d3 --- /dev/null +++ b/src/views/assets/components/BatchExportDialog.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/src/views/assets/components/BatchImportDialog.vue b/src/views/assets/components/BatchImportDialog.vue new file mode 100644 index 0000000..24bbfe2 --- /dev/null +++ b/src/views/assets/components/BatchImportDialog.vue @@ -0,0 +1,458 @@ + + + + + diff --git a/src/views/assets/components/MaintenanceDialog.vue b/src/views/assets/components/MaintenanceDialog.vue new file mode 100644 index 0000000..90cce1d --- /dev/null +++ b/src/views/assets/components/MaintenanceDialog.vue @@ -0,0 +1,264 @@ + + + + + diff --git a/src/views/assets/components/QrcodeDialog.vue b/src/views/assets/components/QrcodeDialog.vue new file mode 100644 index 0000000..0cc92dc --- /dev/null +++ b/src/views/assets/components/QrcodeDialog.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/src/views/auth/Login.vue b/src/views/auth/Login.vue new file mode 100644 index 0000000..9b17a93 --- /dev/null +++ b/src/views/auth/Login.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/src/views/error/404.vue b/src/views/error/404.vue new file mode 100644 index 0000000..b38e6aa --- /dev/null +++ b/src/views/error/404.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/views/examples/ChartsExample.vue b/src/views/examples/ChartsExample.vue new file mode 100644 index 0000000..2d29c56 --- /dev/null +++ b/src/views/examples/ChartsExample.vue @@ -0,0 +1,377 @@ + + + + + + + diff --git a/src/views/examples/DynamicFormExample.vue b/src/views/examples/DynamicFormExample.vue new file mode 100644 index 0000000..09d577b --- /dev/null +++ b/src/views/examples/DynamicFormExample.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/src/views/system/NotificationCenter.vue b/src/views/system/NotificationCenter.vue new file mode 100644 index 0000000..b210e4f --- /dev/null +++ b/src/views/system/NotificationCenter.vue @@ -0,0 +1,507 @@ + + + + + diff --git a/src/views/system/OperationLog.vue b/src/views/system/OperationLog.vue new file mode 100644 index 0000000..ff671e5 --- /dev/null +++ b/src/views/system/OperationLog.vue @@ -0,0 +1,348 @@ + + + + + diff --git a/src/views/system/SystemConfig.vue b/src/views/system/SystemConfig.vue new file mode 100644 index 0000000..6bc430b --- /dev/null +++ b/src/views/system/SystemConfig.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/tests/e2e/assets.spec.ts b/tests/e2e/assets.spec.ts new file mode 100644 index 0000000..22e1259 --- /dev/null +++ b/tests/e2e/assets.spec.ts @@ -0,0 +1,364 @@ +/** + * 资产管理E2E测试 + * + * 测试内容: + * - 资产列表查看 + * - 创建资产 + * - 编辑资产 + * - 删除资产 + * - 资产搜索 + * - 资产分配 + * - 批量导入 + * - 扫码查询 + */ + +import { test, expect } from '@playwright/test' + +test.describe('资产管理E2E测试', () => { + // 在每个测试前登录 + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:5173/login') + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + await page.waitForURL('http://localhost:5173/') + }) + + test('应该显示资产列表', async ({ page }) => { + // 导航到资产列表页 + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 等待列表加载 + await expect(page.locator('.asset-list')).toBeVisible() + await expect(page.locator('.el-table')).toBeVisible() + + // 验证统计数据 + await expect(page.locator('.asset-statistics')).toBeVisible() + }) + + test('应该搜索资产', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 输入搜索关键词 + await page.fill('input[placeholder="搜索资产编码/名称/型号"]', '联想') + await page.click('button:has-text("搜索")') + + // 等待搜索结果 + await page.waitForTimeout(500) + + // 验证搜索结果 + const tableRows = await page.locator('.el-table__body-wrapper .el-table__row').count() + expect(tableRows).toBeGreaterThan(0) + }) + + test('应该创建新资产', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 点击创建按钮 + await page.click('button:has-text("新增资产")') + + // 等待对话框打开 + await expect(page.locator('.el-dialog')).toBeVisible() + + // 填写表单 + await page.selectOption('select[name="deviceType"]', '1') + await page.fill('input[name="assetName"]', '测试资产-E2E') + await page.fill('input[name="model"]', '测试型号') + await page.fill('input[name="serialNumber"]', 'SN-E2E-001') + await page.selectOption('select[name="organization"]', '1') + await page.fill('input[name="location"]', '测试位置') + + // 如果有动态字段 + await page.fill('input[name="cpu"]', 'Intel i5-10400') + await page.selectOption('select[name="memory"]', '16') + + // 提交表单 + await page.click('button:has-text("确定")') + + // 等待成功提示 + await expect(page.locator('.el-message--success')).toBeVisible() + await expect(page.locator('.el-message--success')).toContainText('创建成功') + + // 验证新资产出现在列表中 + await expect(page.locator('text=测试资产-E2E')).toBeVisible() + }) + + test('应该编辑资产', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 点击第一行的编辑按钮 + await page.click('.el-table__row:first-child .edit-button') + + // 等待编辑对话框 + await expect(page.locator('.el-dialog')).toBeVisible() + + // 修改资产名称 + await page.fill('input[name="assetName"]', '更新后的资产名称') + + // 提交修改 + await page.click('button:has-text("确定")') + + // 等待成功提示 + await expect(page.locator('.el-message--success')).toBeVisible() + + // 验证修改已生效 + await expect(page.locator('text=更新后的资产名称')).toBeVisible() + }) + + test('应该删除资产', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 获取初始行数 + const initialRows = await page.locator('.el-table__body-wrapper .el-table__row').count() + + // 点击第一行的删除按钮 + await page.click('.el-table__row:first-child .delete-button') + + // 确认删除 + await page.click('.el-message-box__btns button:has-text("确定")') + + // 等待成功提示 + await expect(page.locator('.el-message--success')).toBeVisible() + + // 验证行数减少 + const finalRows = await page.locator('.el-table__body-wrapper .el-table__row').count() + expect(finalRows).toBe(initialRows - 1) + }) + + test('应该支持分页', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 等待列表加载 + await expect(page.locator('.el-table')).toBeVisible() + + // 点击下一页 + await page.click('.el-pagination .btn-next') + + // 等待加载 + await page.waitForTimeout(500) + + // 验证页码改变 + const currentPage = await page.locator('.el-pager .number.active').textContent() + expect(parseInt(currentPage || '0')).toBeGreaterThan(1) + }) + + test('应该按状态筛选', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 选择状态筛选 + await page.click('.el-select:has-text="资产状态")') + await page.click('text=使用中') + + // 等待筛选结果 + await page.waitForTimeout(500) + + // 验证筛选结果 + const statusCells = await page.locator('.el-table__body .el-table__cell:last-child').allTextContents() + statusCells.forEach(status => { + expect(status).toContain('使用中') + }) + }) + + test('应该按设备类型筛选', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 选择设备类型筛选 + await page.click('.el-select:has-text="设备类型")') + await page.click('text=计算机') + + // 等待筛选结果 + await page.waitForTimeout(500) + + // 验证筛选标签已显示 + await expect(page.locator('.filter-tag:has-text("计算机")')).toBeVisible() + }) + + test('应该查看资产详情', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 点击第一行查看详情 + await page.click('.el-table__row:first-child .detail-button') + + // 等待详情对话框 + await expect(page.locator('.el-dialog')).toBeVisible() + + // 验证详情信息 + await expect(page.locator('.asset-detail')).toBeVisible() + await expect(page.locator('.asset-detail .asset-code')).toBeVisible() + await expect(page.locator('.asset-detail .status-history')).toBeVisible() + }) + + test('应该批量导入资产', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 点击批量导入按钮 + await page.click('button:has-text("批量导入")') + + // 等待上传对话框 + await expect(page.locator('.el-dialog')).toBeVisible() + + // 选择文件 + const fileInput = page.locator('input[type="file"]') + await fileInput.setInputFiles('tests/fixtures/test_assets.xlsx') + + // 点击上传 + await page.click('button:has-text("确定")') + + // 等待上传完成 + await page.waitForTimeout(2000) + + // 验证成功提示 + await expect(page.locator('.el-message--success')).toBeVisible() + }) + + test('应该导出资产', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 设置下载处理 + const downloadPromise = page.waitForEvent('download') + + // 点击导出按钮 + await page.click('button:has-text("导出")') + + // 等待下载开始 + const download = await downloadPromise + + // 验证下载文件 + expect(download.suggestedFilename()).toMatch(/资产.*\.xlsx/) + }) + + test('应该刷新列表', async ({ page }) => { + await page.click('text=资产管理') + await page.click('text=资产列表') + + // 等待列表加载 + await expect(page.locator('.el-table')).toBeVisible() + + // 点击刷新按钮 + await page.click('button:has-text("刷新")') + + // 验证loading状态 + await expect(page.locator('.loading')).toBeVisible() + + // 等待刷新完成 + await page.waitForSelector('.loading', { state: 'hidden' }) + }) +}) + +test.describe('资产分配流程测试', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:5173/login') + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + await page.waitForURL('http://localhost:5173/') + }) + + test('应该创建资产分配单', async ({ page }) => { + await page.click('text=资产分配') + await page.click('text=分配列表') + + // 点击创建分配单 + await page.click('button:has-text("新建分配单")') + + // 等待对话框 + await expect(page.locator('.el-dialog')).toBeVisible() + + // 选择目标网点 + await page.click('select[name="targetOrganization"]') + await page.click('text=天河网点') + + // 选择要分配的资产 + await page.click('.asset-selector button:has-text("选择资产")') + + // 勾选资产 + await page.check('.asset-list .el-checkbox:first-child') + + // 确认选择 + await page.click('button:has-text("确定")') + + // 填写备注 + await page.fill('textarea[name="remark"]', '业务需要分配') + + // 提交分配单 + await page.click('button:has-text("提交")') + + // 验证成功 + await expect(page.locator('.el-message--success')).toBeVisible() + }) + + test('应该审批分配单', async ({ page }) => { + await page.click('text=资产分配') + await page.click('text=待审批') + + // 点击第一条记录的审批按钮 + await page.click('.allocation-item:first-child .approve-button') + + // 等待审批对话框 + await expect(page.locator('.el-dialog')).toBeVisible() + + // 选择审批结果 + await page.click('input[value="approved"]') + + // 填写审批意见 + await page.fill('textarea[name="approvalRemark"]', '同意分配') + + // 提交审批 + await page.click('button:has-text("确定")') + + // 验证成功 + await expect(page.locator('.el-message--success')).toBeVisible() + }) +}) + +test.describe('扫码查询测试', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:5173/login') + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + await page.waitForURL('http://localhost:5173/') + }) + + test('应该扫码查询资产', async ({ page }) => { + // 导航到扫码页面 + await page.click('text=扫码查询') + + // 等待摄像头权限请求 + // 在测试环境中我们模拟扫码 + + // 手动输入资产编码模拟扫码结果 + await page.click('button:has-text("手动输入")') + await page.fill('input[name="assetCode"]', 'ASSET-20250124-0001') + await page.click('button:has-text("查询")') + + // 验证资产详情显示 + await expect(page.locator('.asset-detail')).toBeVisible() + await expect(page.locator('text=ASSET-20250124-0001')).toBeVisible() + }) + + test('应该处理不存在的资产编码', async ({ page }) => { + await page.click('text=扫码查询') + await page.click('button:has-text("手动输入")') + await page.fill('input[name="assetCode"]', 'INVALID-CODE') + await page.click('button:has-text("查询")') + + // 验证错误提示 + await expect(page.locator('.el-message--error')).toBeVisible() + await expect(page.locator('.el-message--error')).toContainText('资产不存在') + }) +}) diff --git a/tests/e2e/global-setup.ts b/tests/e2e/global-setup.ts new file mode 100644 index 0000000..b1da32a --- /dev/null +++ b/tests/e2e/global-setup.ts @@ -0,0 +1,29 @@ +/** + * Playwright E2E测试 - 全局设置 + * + * 在所有测试运行前执行 + */ + +import { FullConfig } from '@playwright/test' + +async function globalSetup(config: FullConfig) { + console.log('🚀 开始E2E测试全局设置...') + + // 可以在这里进行测试前的准备工作: + // 1. 启动测试数据库 + // 2. 运行数据库迁移 + // 3. 准备测试数据 + // 4. 启动后端服务 + // 5. 启动前端服务(通常由playwright.config.ts配置) + + const baseURL = config.projects?.[0]?.use?.baseURL || 'http://localhost:5173' + + console.log(`📝 测试基础URL: ${baseURL}`) + + // 等待服务启动 + await new Promise(resolve => setTimeout(resolve, 2000)) + + console.log('✅ E2E测试全局设置完成!') +} + +export default globalSetup diff --git a/tests/e2e/global-teardown.ts b/tests/e2e/global-teardown.ts new file mode 100644 index 0000000..c172a44 --- /dev/null +++ b/tests/e2e/global-teardown.ts @@ -0,0 +1,26 @@ +/** + * Playwright E2E测试 - 全局清理 + * + * 在所有测试运行后执行 + */ + +import { FullConfig } from '@playwright/test' + +async function globalTeardown(config: FullConfig) { + console.log('🧹 开始E2E测试全局清理...') + + // 可以在这里进行测试后的清理工作: + // 1. 清理测试数据库 + // 2. 关闭测试服务 + // 3. 删除临时文件 + // 4. 归档测试报告 + + console.log('📊 生成测试报告摘要...') + + // 这里可以添加报告汇总逻辑 + + console.log('✅ E2E测试全局清理完成!') + console.log('📄 测试报告位于: test_reports/playwright-report/index.html') +} + +export default globalTeardown diff --git a/tests/e2e/login.spec.ts b/tests/e2e/login.spec.ts new file mode 100644 index 0000000..a6d10ec --- /dev/null +++ b/tests/e2e/login.spec.ts @@ -0,0 +1,258 @@ +/** + * 登录流程E2E测试 + * + * 测试内容: + * - 正常登录流程 + * - 错误密码处理 + * - 验证码验证 + * - Token过期处理 + * - 记住密码功能 + */ + +import { test, expect } from '@playwright/test' + +test.describe('登录流程测试', () => { + test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:5173/login') + }) + + test('应该成功登录并跳转到首页', async ({ page }) => { + // 输入用户名和密码 + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + + // 输入验证码 (假设测试环境验证码固定为1234) + await page.fill('input[name="captcha"]', '1234') + + // 点击登录按钮 + await page.click('button[type="submit"]') + + // 等待跳转 + await page.waitForURL('http://localhost:5173/') + + // 验证URL已跳转到首页 + expect(page.url()).toBe('http://localhost:5173/') + + // 验证显示了用户信息 + await expect(page.locator('.user-info')).toBeVisible() + await expect(page.locator('.user-info')).toContainText('admin') + }) + + test('应该显示用户名或密码错误', async ({ page }) => { + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'WrongPassword') + await page.fill('input[name="captcha"]', '1234') + + await page.click('button[type="submit"]') + + // 等待错误消息显示 + await expect(page.locator('.el-message--error')).toBeVisible() + await expect(page.locator('.el-message--error')).toContainText('用户名或密码错误') + }) + + test('应该验证必填字段', async ({ page }) => { + // 不填写任何字段直接提交 + await page.click('button[type="submit"]') + + // 验证表单验证错误 + await expect(page.locator('input[name="username"] + .el-form-item__error')).toBeVisible() + await expect(page.locator('input[name="password"] + .el-form-item__error')).toBeVisible() + }) + + test('应该验证用户名格式', async ({ page }) => { + // 输入无效用户名 + await page.fill('input[name="username"]', 'ab') // 太短 + await page.fill('input[name="password"]', 'Admin123') + + await page.click('button[type="submit"]') + + // 应该显示用户名格式错误 + await expect(page.locator('.el-form-item__error')).toBeVisible() + }) + + test('应该验证密码强度', async ({ page }) => { + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'weak') // 弱密码 + + await page.click('button[type="submit"]') + + // 应该显示密码强度错误 + await expect(page.locator('.el-form-item__error')).toBeVisible() + }) + + test('应该刷新验证码', async ({ page }) => { + const captchaImage = page.locator('.captcha-image img') + + // 获取初始验证码图片URL + const initialSrc = await captchaImage.getAttribute('src') + + // 点击刷新验证码 + await page.click('.refresh-captcha') + + // 等待图片重新加载 + await page.waitForLoadState('networkidle') + + // 验证验证码已更新 + const newSrc = await captchaImage.getAttribute('src') + expect(newSrc).not.toBe(initialSrc) + }) + + test('应该支持记住密码功能', async ({ page }) => { + // 勾选记住密码 + await page.check('input[name="remember"]') + + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + + await page.click('button[type="submit"]') + await page.waitForURL('http://localhost:5173/') + + // 验证localStorage中保存了用户信息 + const rememberedUser = await page.evaluate(() => { + return localStorage.getItem('rememberedUser') + }) + + expect(rememberedUser).toBeTruthy() + + // 退出登录 + await page.click('.logout-button') + + // 返回登录页 + await page.goto('http://localhost:5173/login') + + // 验证用户名已填充 + const username = await page.inputValue('input[name="username"]') + expect(username).toBe('admin') + }) + + test('应该处理验证码错误', async ({ page }) => { + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '9999') // 错误验证码 + + await page.click('button[type="submit"]') + + // 应该显示验证码错误 + await expect(page.locator('.el-message--error')).toBeVisible() + await expect(page.locator('.el-message--error')).toContainText('验证码错误') + }) + + test('应该限制登录尝试次数', async ({ page }) => { + // 尝试多次错误登录 + for (let i = 0; i < 5; i++) { + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'WrongPassword') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + + await page.waitForTimeout(500) + } + + // 第6次应该被锁定 + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + + await expect(page.locator('.el-message--error')).toBeVisible() + await expect(page.locator('.el-message--error')).toContainText('账户已锁定') + }) + + test('应该支持回车键登录', async ({ page }) => { + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + + // 在密码框按回车 + await page.press('input[name="password"]', 'Enter') + + // 应该提交登录 + await page.waitForURL('http://localhost:5173/') + expect(page.url()).toBe('http://localhost:5173/') + }) + + test('应该处理网络错误', async ({ page, context }) => { + // 模拟网络断开 + await context.setOffline(true) + + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + + // 应该显示网络错误 + await expect(page.locator('.el-message--error')).toBeVisible() + await expect(page.locator('.el-message--error')).toContainText('网络错误') + + // 恢复网络 + await context.setOffline(false) + }) +}) + +test.describe('Token过期处理', () => { + test('应该在Token过期时自动刷新', async ({ page }) => { + // 登录 + await page.goto('http://localhost:5173/login') + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + await page.waitForURL('http://localhost:5173/') + + // 模拟Token过期(通过设置过期Token) + await page.evaluate(() => { + localStorage.setItem('token', 'expired_token') + localStorage.setItem('refreshToken', 'valid_refresh_token') + }) + + // 刷新页面 + await page.reload() + + // 应该自动刷新Token并正常显示 + await expect(page.locator('.user-info')).toBeVisible() + }) + + test('应该在刷新Token失败时跳转登录页', async ({ page }) => { + await page.goto('http://localhost:5173/') + await page.evaluate(() => { + localStorage.setItem('token', 'expired_token') + localStorage.setItem('refreshToken', 'expired_refresh_token') + }) + + // 刷新页面 + await page.reload() + + // 应该跳转到登录页 + await page.waitForURL('http://localhost:5173/login') + expect(page.url()).toBe('http://localhost:5173/login') + }) +}) + +test.describe('跨浏览器测试', () => { + test('应该在Chrome中正常工作', async ({ page, browserName }) => { + test.skip(browserName !== 'chromium') + + await page.goto('http://localhost:5173/login') + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + + await page.waitForURL('http://localhost:5173/') + expect(page.url()).toBe('http://localhost:5173/') + }) + + test('应该在Firefox中正常工作', async ({ page, browserName }) => { + test.skip(browserName !== 'firefox') + + await page.goto('http://localhost:5173/login') + await page.fill('input[name="username"]', 'admin') + await page.fill('input[name="password"]', 'Admin123') + await page.fill('input[name="captcha"]', '1234') + await page.click('button[type="submit"]') + + await page.waitForURL('http://localhost:5173/') + expect(page.url()).toBe('http://localhost:5173/') + }) +}) diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..65df66d --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,190 @@ +/** + * Vitest 测试环境设置 + * + * 配置全局测试环境和工具 + */ + +import { vi } from 'vitest' + +// Mock window.matchMedia +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // Deprecated + removeListener: vi.fn(), // Deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}) + +// Mock IntersectionObserver +global.IntersectionObserver = class IntersectionObserver { + constructor() {} + disconnect() {} + observe() {} + takeRecords() { + return [] + } + unobserve() {} +} as any + +// Mock ResizeObserver +global.ResizeObserver = class ResizeObserver { + constructor() {} + disconnect() {} + observe() {} + unobserve() {} +} as any + +// Mock localStorage +const localStorageMock = { + getItem: (key: string) => null, + setItem: (key: string, value: string) => {}, + removeItem: (key: string) => {}, + clear: () => {}, +} + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock, +}) + +// Mock sessionStorage +const sessionStorageMock = { + getItem: (key: string) => null, + setItem: (key: string, value: string) => {}, + removeItem: (key: string) => {}, + clear: () => {}, +} + +Object.defineProperty(window, 'sessionStorage', { + value: sessionStorageMock, +}) + +// Mock requestAnimationFrame +global.requestAnimationFrame = (callback: FrameRequestCallback) => { + return setTimeout(callback, 16) as unknown as number +} + +global.cancelAnimationFrame = (id: number) => { + clearTimeout(id) +} + +// Mock console方法以减少测试输出 +global.console = { + ...console, + // Uncomment to ignore console logs during tests + // log: vi.fn(), + // debug: vi.fn(), + // info: vi.fn(), + // warn: vi.fn(), + // error: vi.fn(), +} + +// 设置全局错误处理 +window.addEventListener('error', event => { + console.error('Global error:', event.error) +}) + +// 设置未处理的Promise rejection +window.addEventListener('unhandledrejection', event => { + console.error('Unhandled promise rejection:', event.reason) +}) + +// Mock URL.createObjectURL和URL.revokeObjectURL +global.URL.createObjectURL = vi.fn(() => 'mock-url') +global.URL.revokeObjectURL = vi.fn() + +// Mock navigator +Object.defineProperty(window.navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + writable: true, +}) + +// Mock scrollTo +window.scrollTo = vi.fn() + +// Mock getComputedStyle +window.getComputedStyle = vi.fn(() => ({ + getPropertyValue: (property: string) => { + const styles: Record = { + display: 'block', + width: '100px', + height: '100px', + } + return styles[property] || '' + }, +})) + +// Mock DOMRect +global.DOMRect = class DOMRect { + constructor( + public x = 0, + public y = 0, + public width = 0, + public height = 0, + ) {} + + toJSON() { + return { + x: this.x, + y: this.y, + top: this.y, + left: this.x, + bottom: this.y + this.height, + right: this.x + this.width, + width: this.width, + height: this.height, + } + } + + static fromRect(rect?: DOMRectInit): DOMRect { + return new DOMRect(rect?.x, rect?.y, rect?.width, rect?.height) + } +} as any + +// Element.prototype.getBoundingClientRect mock +Element.prototype.getBoundingClientRect = new DOMRect(0, 0, 0, 0) as any + +// 每个测试前清理 +beforeEach(() => { + vi.clearAllMocks() +}) + +// 导出测试工具 +export const TestUtils = { + /** + * 等待组件更新 + */ + async flushPromises(): Promise { + return new Promise(resolve => setTimeout(resolve, 0)) + }, + + /** + * 创建Mock响应 + */ + createMockResponse(data: T, status = 200): Response { + return { + ok: status >= 200 && status < 300, + status, + statusText: status === 200 ? 'OK' : 'Error', + json: async () => data, + data, + } as any + }, + + /** + * 创建Mock API响应 + */ + createMockApiResponse(data: T, code = 200, message = 'success') { + return { + code, + message, + data, + timestamp: Date.now(), + } + }, +} diff --git a/tests/unit/components/AssetList.test.ts b/tests/unit/components/AssetList.test.ts new file mode 100644 index 0000000..542ab3c --- /dev/null +++ b/tests/unit/components/AssetList.test.ts @@ -0,0 +1,339 @@ +/** + * 资产列表组件测试 + * + * 测试内容: + * - 组件渲染 + * - 数据加载 + * - 搜索功能 + * - 分页功能 + * - 事件触发 + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount, VueWrapper } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import ElementPlus from 'element-plus' +import AssetList from '@/views/assets/AssetList.vue' +import * as assetApi from '@/api/assets' + +// Mock API模块 +vi.mock('@/api/assets', () => ({ + getAssetList: vi.fn(), + deleteAsset: vi.fn(), + getAssetStatistics: vi.fn() +})) + +describe('AssetList组件', () => { + let wrapper: VueWrapper + let pinia: any + + beforeEach(() => { + // 创建新的Pinia实例 + pinia = createPinia() + setActivePinia(pinia) + + // Mock API响应 + vi.mocked(assetApi.getAssetList).mockResolvedValue({ + items: [ + { + id: 1, + assetCode: 'ASSET-20250124-0001', + assetName: '联想台式机', + deviceType: { id: 1, typeName: '计算机' }, + organization: { id: 1, orgName: '天河网点' }, + status: 'in_use', + purchaseDate: '2024-01-15', + purchasePrice: 4500.00 + } + ], + total: 1, + page: 1, + pageSize: 20 + }) + + vi.mocked(assetApi.getAssetStatistics).mockResolvedValue({ + totalCount: 100, + totalValue: 500000.00, + statusDistribution: { + in_stock: 30, + in_use: 50, + maintenance: 10, + scrapped: 10 + } + }) + }) + + afterEach(() => { + if (wrapper) { + wrapper.unmount() + } + vi.clearAllMocks() + }) + + it('应该正确渲染组件', () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-pagination': true, + 'el-input': true, + 'el-button': true + } + } + }) + + expect(wrapper.find('.asset-list').exists()).toBe(true) + }) + + it('应该在挂载时加载资产列表', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-pagination': true + } + } + }) + + await wrapper.vm.$nextTick() + + expect(assetApi.getAssetList).toHaveBeenCalledWith({ + page: 1, + page_size: 20 + }) + }) + + it('应该显示资产统计数据', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-statistic': true + } + } + }) + + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() // 等待统计数据加载 + + expect(assetApi.getAssetStatistics).toHaveBeenCalled() + }) + + it('应该支持搜索功能', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-input': true, + 'el-button': true + } + } + }) + + const searchKeyword = '联想' + wrapper.vm.searchKeyword = searchKeyword + await wrapper.vm.handleSearch() + + expect(assetApi.getAssetList).toHaveBeenCalledWith({ + page: 1, + page_size: 20, + keyword: searchKeyword + }) + }) + + it('应该支持分页功能', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-pagination': true + } + } + }) + + wrapper.vm.pagination.page = 2 + await wrapper.vm.fetchAssets() + + expect(assetApi.getAssetList).toHaveBeenCalledWith({ + page: 2, + page_size: 20 + }) + }) + + it('应该触发刷新事件', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-button': true + } + } + }) + + const refreshSpy = vi.spyOn(wrapper.vm, 'fetchAssets') + await wrapper.vm.handleRefresh() + + expect(refreshSpy).toHaveBeenCalled() + }) + + it('应该打开创建对话框', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-button': true, + 'asset-create-dialog': true + } + } + }) + + await wrapper.vm.openCreateDialog() + expect(wrapper.vm.createDialogVisible).toBe(true) + }) + + it('应该打开编辑对话框', async () => { + const mockAsset = { + id: 1, + assetCode: 'ASSET-20250124-0001', + assetName: '联想台式机' + } + + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'asset-edit-dialog': true + } + } + }) + + await wrapper.vm.openEditDialog(mockAsset) + expect(wrapper.vm.editDialogVisible).toBe(true) + expect(wrapper.vm.currentAsset).toEqual(mockAsset) + }) + + it('应该删除资产', async () => { + vi.mocked(assetApi.deleteAsset).mockResolvedValue({}) + + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-button': true + } + } + }) + + // Mock确认对话框 + vi.spyOn(wrapper.vm as any, '$confirm').mockResolvedValue('confirm') + + await wrapper.vm.handleDelete(1) + + expect(assetApi.deleteAsset).toHaveBeenCalledWith(1) + }) + + it('应该在搜索时重置页码', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-input': true + } + } + }) + + wrapper.vm.pagination.page = 5 + wrapper.vm.searchKeyword = '测试' + await wrapper.vm.handleSearch() + + expect(wrapper.vm.pagination.page).toBe(1) + }) + + it('应该显示加载状态', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true + } + } + }) + + wrapper.vm.loading = true + await wrapper.vm.$nextTick() + + expect(wrapper.find('.loading').exists()).toBe(true) + }) + + it('应该处理API错误', async () => { + vi.mocked(assetApi.getAssetList).mockRejectedValue(new Error('网络错误')) + + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true + } + } + }) + + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + + await wrapper.vm.fetchAssets() + + expect(consoleSpy).toHaveBeenCalled() + consoleSpy.mockRestore() + }) + + it('应该支持状态筛选', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-select': true + } + } + }) + + wrapper.vm.filters.status = 'in_use' + await wrapper.vm.handleFilter() + + expect(assetApi.getAssetList).toHaveBeenCalledWith({ + page: 1, + page_size: 20, + status: 'in_use' + }) + }) + + it('应该支持设备类型筛选', async () => { + wrapper = mount(AssetList, { + global: { + plugins: [pinia, ElementPlus], + stubs: { + 'el-table': true, + 'el-select': true + } + } + }) + + wrapper.vm.filters.deviceTypeId = 1 + await wrapper.vm.handleFilter() + + expect(assetApi.getAssetList).toHaveBeenCalledWith({ + page: 1, + page_size: 20, + device_type_id: 1 + }) + }) +}) diff --git a/tests/unit/components/PieChart.test.ts b/tests/unit/components/PieChart.test.ts new file mode 100644 index 0000000..5f2eec4 --- /dev/null +++ b/tests/unit/components/PieChart.test.ts @@ -0,0 +1,124 @@ +/** + * 图表组件单元测试示例 + * 测试 PieChart 组件 + */ + +import { describe, it, expect, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import PieChart from '@/components/charts/PieChart.vue' + +describe('PieChart.vue', () => { + it('renders properly with data', () => { + const wrapper = mount(PieChart, { + props: { + data: [ + { name: '库存中', value: 200 }, + { name: '在用', value: 750 }, + ], + title: '资产状态分布', + }, + }) + + expect(wrapper.find('.base-chart').exists()).toBe(true) + }) + + it('emits click event when clicking on a slice', async () => { + const wrapper = mount(PieChart, { + props: { + data: [ + { name: '库存中', value: 200 }, + { name: '在用', value: 750 }, + ], + }, + }) + + // 模拟点击事件 + // 注意:实际测试需要等待图表渲染完成 + // 这里只是示例 + expect(wrapper.exists()).toBe(true) + }) + + it('renders doughnut chart when type is doughnut', () => { + const wrapper = mount(PieChart, { + props: { + data: [{ name: '测试', value: 100 }], + type: 'doughnut', + }, + }) + + expect(wrapper.props('type')).toBe('doughnut') + }) + + it('renders pie chart when type is pie', () => { + const wrapper = mount(PieChart, { + props: { + data: [{ name: '测试', value: 100 }], + type: 'pie', + }, + }) + + expect(wrapper.props('type')).toBe('pie') + }) + + it('shows legend when showLegend is true', () => { + const wrapper = mount(PieChart, { + props: { + data: [{ name: '测试', value: 100 }], + showLegend: true, + }, + }) + + expect(wrapper.props('showLegend')).toBe(true) + }) + + it('hides legend when showLegend is false', () => { + const wrapper = mount(PieChart, { + props: { + data: [{ name: '测试', value: 100 }], + showLegend: false, + }, + }) + + expect(wrapper.props('showLegend')).toBe(false) + }) + + it('uses custom color when customColor is true', () => { + const wrapper = mount(PieChart, { + props: { + data: [ + { name: '库存中', value: 200, status: 'in_stock' }, + { name: '在用', value: 750, status: 'in_use' }, + ], + customColor: true, + }, + }) + + expect(wrapper.props('customColor')).toBe(true) + }) + + it('applies custom height', () => { + const wrapper = mount(PieChart, { + props: { + data: [{ name: '测试', value: 100 }], + height: '500px', + }, + }) + + expect(wrapper.props('height')).toBe('500px') + }) + + it('emits ready event when chart is ready', async () => { + const wrapper = mount(PieChart, { + props: { + data: [{ name: '测试', value: 100 }], + }, + }) + + // 等待组件挂载 + await wrapper.vm.$nextTick() + + // 检查事件是否被触发 + // 注意:实际测试需要等待 ECharts 初始化完成 + expect(wrapper.exists()).toBe(true) + }) +}) diff --git a/tests/unit/components/form/DynamicFieldRenderer.test.ts b/tests/unit/components/form/DynamicFieldRenderer.test.ts new file mode 100644 index 0000000..83ac48f --- /dev/null +++ b/tests/unit/components/form/DynamicFieldRenderer.test.ts @@ -0,0 +1,823 @@ +/** + * DynamicFieldRenderer 组件测试 + * + * 测试范围: + * - 基础渲染 (10+用例) + * - 不同字段类型渲染 (15+用例) + * - 数据绑定 (10+用例) + * - 验证功能 (10+用例) + * - 依赖处理 (5+用例) + * + * 总计: 40+ 用例 + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount, VueWrapper } from '@vue/test-utils' +import { nextTick } from 'vue' +import DynamicFieldRenderer from '@/components/form/DynamicFieldRenderer.vue' +import { FieldConfig, FieldType } from '@/types/form' + +describe('DynamicFieldRenderer 组件测试', () => { + // 测试数据 + const mockFieldConfig: FieldConfig = { + field_id: 'test_field_1', + field_name: '测试字段', + field_type: FieldType.TEXT, + is_required: true, + placeholder: '请输入测试字段', + validation_rules: [ + { + rule_type: 'length', + rule_value: { min: 1, max: 100 } + } + ] + } + + const mockModelValue = ref('') + + // 基础渲染测试 (10+用例) + describe('基础渲染', () => { + it('应该正确渲染组件', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: mockModelValue.value + } + }) + expect(wrapper.exists()).toBe(true) + }) + + it('应该显示字段标签', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '' + } + }) + expect(wrapper.text()).toContain('测试字段') + }) + + it('应该显示必填标记', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '' + } + }) + expect(wrapper.find('.required-mark').exists()).toBe(true) + }) + + it('不应该显示非必填字段的必填标记', () => { + const config = { ...mockFieldConfig, is_required: false } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('.required-mark').exists()).toBe(false) + }) + + it('应该显示字段提示信息', () => { + const config = { + ...mockFieldConfig, + help_text: '这是字段的帮助文本' + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.text()).toContain('这是字段的帮助文本') + }) + + it('应该应用自定义CSS类', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '', + customClass: 'custom-field-class' + } + }) + expect(wrapper.classes()).toContain('custom-field-class') + }) + + it('应该显示字段描述', () => { + const config = { + ...mockFieldConfig, + description: '字段详细描述' + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.text()).toContain('字段详细描述') + }) + + it('应该在禁用状态下渲染', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '', + disabled: true + } + }) + const input = wrapper.find('input') + expect(input.attributes('disabled')).toBeDefined() + }) + + it('应该在只读状态下渲染', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: 'test value', + readonly: true + } + }) + const input = wrapper.find('input') + expect(input.attributes('readonly')).toBeDefined() + }) + + it('应该响应字段配置变化', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '' + } + }) + + await wrapper.setProps({ + fieldConfig: { + ...mockFieldConfig, + field_name: '更新后的字段名' + } + }) + + await nextTick() + expect(wrapper.text()).toContain('更新后的字段名') + }) + }) + + // 不同字段类型渲染测试 (15+用例) + describe('不同字段类型渲染', () => { + it('应该渲染文本输入框', () => { + const config = { ...mockFieldConfig, field_type: FieldType.TEXT } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('input[type="text"]').exists()).toBe(true) + }) + + it('应该渲染数字输入框', () => { + const config = { ...mockFieldConfig, field_type: FieldType.NUMBER } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: 0 + } + }) + expect(wrapper.find('input[type="number"]').exists()).toBe(true) + }) + + it('应该渲染日期选择器', () => { + const config = { ...mockFieldConfig, field_type: FieldType.DATE } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('.date-picker').exists()).toBe(true) + }) + + it('应该渲染下拉选择框', () => { + const config = { + ...mockFieldConfig, + field_type: FieldType.SELECT, + options: [ + { label: '选项1', value: 'option1' }, + { label: '选项2', value: 'option2' } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('select').exists()).toBe(true) + }) + + it('应该渲染多选框', () => { + const config = { + ...mockFieldConfig, + field_type: FieldType.MULTI_SELECT, + options: [ + { label: '选项1', value: 'option1' }, + { label: '选项2', value: 'option2' } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: [] + } + }) + expect(wrapper.find('.multi-select').exists()).toBe(true) + }) + + it('应该渲染单选框组', () => { + const config = { + ...mockFieldConfig, + field_type: FieldType.RADIO, + options: [ + { label: '选项1', value: 'option1' }, + { label: '选项2', value: 'option2' } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('.radio-group').exists()).toBe(true) + }) + + it('应该渲染复选框', () => { + const config = { ...mockFieldConfig, field_type: FieldType.CHECKBOX } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: false + } + }) + expect(wrapper.find('input[type="checkbox"]').exists()).toBe(true) + }) + + it('应该渲染文本域', () => { + const config = { ...mockFieldConfig, field_type: FieldType.TEXTAREA } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('textarea').exists()).toBe(true) + }) + + it('应该渲染富文本编辑器', () => { + const config = { ...mockFieldConfig, field_type: FieldType.RICH_TEXT } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('.rich-text-editor').exists()).toBe(true) + }) + + it('应该渲染文件上传组件', () => { + const config = { ...mockFieldConfig, field_type: FieldType.FILE_UPLOAD } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: [] + } + }) + expect(wrapper.find('.file-upload').exists()).toBe(true) + }) + + it('应该渲染日期时间选择器', () => { + const config = { ...mockFieldConfig, field_type: FieldType.DATETIME } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('.datetime-picker').exists()).toBe(true) + }) + + it('应该渲染时间选择器', () => { + const config = { ...mockFieldConfig, field_type: FieldType.TIME } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + expect(wrapper.find('.time-picker').exists()).toBe(true) + }) + + it('应该渲染滑块', () => { + const config = { + ...mockFieldConfig, + field_type: FieldType.SLIDER, + validation_rules: [ + { + rule_type: 'range', + rule_value: { min: 0, max: 100 } + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: 50 + } + }) + expect(wrapper.find('.slider').exists()).toBe(true) + }) + + it('应该渲染开关', () => { + const config = { ...mockFieldConfig, field_type: FieldType.SWITCH } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: false + } + }) + expect(wrapper.find('.switch').exists()).toBe(true) + }) + + it('应该渲染级联选择器', () => { + const config = { + ...mockFieldConfig, + field_type: FieldType.CASCADER, + options: [ + { + label: '级别1', + value: '1', + children: [ + { label: '级别2-1', value: '1-1' }, + { label: '级别2-2', value: '1-2' } + ] + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: [] + } + }) + expect(wrapper.find('.cascader').exists()).toBe(true) + }) + }) + + // 数据绑定测试 (10+用例) + describe('数据绑定', () => { + it('应该正确绑定modelValue', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: 'initial value' + } + }) + const input = wrapper.find('input') + expect(input.element.value).toBe('initial value') + }) + + it('应该在输入时触发update:modelValue事件', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '' + } + }) + const input = wrapper.find('input') + await input.setValue('new value') + expect(wrapper.emitted('update:modelValue')).toBeTruthy() + expect(wrapper.emitted('update:modelValue')![0]).toEqual(['new value']) + }) + + it('应该响应modelValue的变化', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: 'initial' + } + }) + + await wrapper.setProps({ modelValue: 'updated' }) + await nextTick() + + const input = wrapper.find('input') + expect(input.element.value).toBe('updated') + }) + + it('应该正确处理数字类型的值', async () => { + const config = { ...mockFieldConfig, field_type: FieldType.NUMBER } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: 0 + } + }) + + const input = wrapper.find('input') + await input.setValue('123') + expect(wrapper.emitted('update:modelValue')![0]).toEqual([123]) + }) + + it('应该正确处理布尔类型的值', async () => { + const config = { ...mockFieldConfig, field_type: FieldType.CHECKBOX } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: false + } + }) + + const checkbox = wrapper.find('input[type="checkbox"]') + await checkbox.setChecked() + expect(wrapper.emitted('update:modelValue')![0]).toEqual([true]) + }) + + it('应该正确处理数组类型的值', async () => { + const config = { + ...mockFieldConfig, + field_type: FieldType.MULTI_SELECT, + options: [ + { label: '选项1', value: 'option1' }, + { label: '选项2', value: 'option2' } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: [] + } + }) + + // 模拟选择操作 + wrapper.vm.handleSelect('option1') + await nextTick() + + expect(wrapper.emitted('update:modelValue')).toBeTruthy() + }) + + it('应该正确处理日期类型的值', async () => { + const config = { ...mockFieldConfig, field_type: FieldType.DATE } + const dateValue = '2025-01-24' + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '' + } + }) + + wrapper.vm.handleDateChange(dateValue) + await nextTick() + + expect(wrapper.emitted('update:modelValue')![0]).toEqual([dateValue]) + }) + + it('应该在清空时触发正确的事件', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: 'test' + } + }) + + await wrapper.vm.clearValue() + expect(wrapper.emitted('update:modelValue')![0]).toEqual(['']) + }) + + it('应该正确处理空值', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: null + } + }) + + const input = wrapper.find('input') + expect(input.element.value).toBe('') + }) + + it('应该正确处理未定义的值', () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: undefined + } + }) + + const input = wrapper.find('input') + expect(input.element.value).toBe('') + }) + }) + + // 验证功能测试 (10+用例) + describe('验证功能', () => { + it('应该验证必填字段', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '' + } + }) + + const isValid = await wrapper.vm.validate() + expect(isValid).toBe(false) + }) + + it('应该通过必填字段的验证', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: 'test value' + } + }) + + const isValid = await wrapper.vm.validate() + expect(isValid).toBe(true) + }) + + it('应该验证最小长度', async () => { + const config = { + ...mockFieldConfig, + validation_rules: [ + { + rule_type: 'length', + rule_value: { min: 5, max: 100 } + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: 'abc' + } + }) + + const isValid = await wrapper.vm.validate() + expect(isValid).toBe(false) + }) + + it('应该验证最大长度', async () => { + const config = { + ...mockFieldConfig, + validation_rules: [ + { + rule_type: 'length', + rule_value: { min: 1, max: 10 } + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: 'a'.repeat(20) + } + }) + + const isValid = await wrapper.vm.validate() + expect(isValid).toBe(false) + }) + + it('应该验证数字范围', async () => { + const config = { + ...mockFieldConfig, + field_type: FieldType.NUMBER, + validation_rules: [ + { + rule_type: 'range', + rule_value: { min: 1, max: 100 } + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: 150 + } + }) + + const isValid = await wrapper.vm.validate() + expect(isValid).toBe(false) + }) + + it('应该验证正则表达式', async () => { + const config = { + ...mockFieldConfig, + validation_rules: [ + { + rule_type: 'regex', + rule_value: '^[A-Z0-9]+$' + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: 'invalid-value' + } + }) + + const isValid = await wrapper.vm.validate() + expect(isValid).toBe(false) + }) + + it('应该显示验证错误信息', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '' + } + }) + + await wrapper.vm.validate() + await nextTick() + + expect(wrapper.find('.error-message').exists()).toBe(true) + expect(wrapper.text()).toContain('必填') + }) + + it('应该支持自定义验证规则', async () => { + const customValidator = vi.fn().mockResolvedValue(false) + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: 'test', + customValidator + } + }) + + const isValid = await wrapper.vm.validate() + expect(customValidator).toHaveBeenCalled() + expect(isValid).toBe(false) + }) + + it('应该在值变化时触发验证', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '', + validateOnBlur: true + } + }) + + const input = wrapper.find('input') + await input.trigger('blur') + await nextTick() + + expect(wrapper.vm.error).toBeTruthy() + }) + + it('应该清除验证错误', async () => { + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: mockFieldConfig, + modelValue: '' + } + }) + + await wrapper.vm.validate() + expect(wrapper.vm.error).toBeTruthy() + + await wrapper.vm.clearError() + expect(wrapper.vm.error).toBeNull() + }) + }) + + // 依赖处理测试 (5+用例) + describe('依赖处理', () => { + it('应该根据依赖条件显示/隐藏字段', async () => { + const config = { + ...mockFieldConfig, + dependencies: [ + { + field_id: 'parent_field', + condition: 'equals', + value: 'show' + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '', + formData: { parent_field: 'hide' } + } + }) + + expect(wrapper.vm.isVisible).toBe(false) + + await wrapper.setProps({ + formData: { parent_field: 'show' } + }) + await nextTick() + + expect(wrapper.vm.isVisible).toBe(true) + }) + + it('应该根据依赖条件启用/禁用字段', async () => { + const config = { + ...mockFieldConfig, + dependencies: [ + { + field_id: 'parent_field', + condition: 'equals', + value: 'enable', + action: 'disable' + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '', + formData: { parent_field: 'disable' } + } + }) + + const input = wrapper.find('input') + expect(input.attributes('disabled')).toBeDefined() + }) + + it('应该根据依赖条件更新字段值', async () => { + const config = { + ...mockFieldConfig, + dependencies: [ + { + field_id: 'parent_field', + condition: 'equals', + value: 'auto', + action: 'set_value', + target_value: 'automatic value' + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '', + formData: { parent_field: 'auto' } + } + }) + + await wrapper.vm.handleDependencies() + expect(wrapper.emitted('update:modelValue')![0]).toEqual(['automatic value']) + }) + + it('应该支持多个依赖条件', async () => { + const config = { + ...mockFieldConfig, + dependencies: [ + { + field_id: 'field1', + condition: 'equals', + value: 'value1' + }, + { + field_id: 'field2', + condition: 'equals', + value: 'value2', + operator: 'AND' + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '', + formData: { field1: 'value1', field2: 'value2' } + } + }) + + expect(wrapper.vm.isVisible).toBe(true) + }) + + it('应该处理复杂的依赖逻辑', async () => { + const config = { + ...mockFieldConfig, + dependencies: [ + { + field_id: 'parent_field', + condition: 'in', + value: ['option1', 'option2', 'option3'] + } + ] + } + const wrapper = mount(DynamicFieldRenderer, { + props: { + fieldConfig: config, + modelValue: '', + formData: { parent_field: 'option2' } + } + }) + + expect(wrapper.vm.isVisible).toBe(true) + }) + }) +}) diff --git a/tests/unit/composables/useAsset.test.ts b/tests/unit/composables/useAsset.test.ts new file mode 100644 index 0000000..be1ffe9 --- /dev/null +++ b/tests/unit/composables/useAsset.test.ts @@ -0,0 +1,303 @@ +/** + * useAsset Composable测试 + * + * 测试内容: + * - 资产列表获取 + * - 资产详情获取 + * - 资产创建 + * - 资产更新 + * - 资产删除 + * - 错误处理 + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { useAsset } from '@/composables/useAsset' +import * as assetApi from '@/api/assets' + +// Mock API模块 +vi.mock('@/api/assets', () => ({ + getAssetList: vi.fn(), + getAssetById: vi.fn(), + createAsset: vi.fn(), + updateAsset: vi.fn(), + deleteAsset: vi.fn(), + importAssets: vi.fn() +})) + +describe('useAsset Composable', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('fetchAssets', () => { + it('应该成功获取资产列表', async () => { + const mockData = { + items: [ + { + id: 1, + assetCode: 'ASSET-20250124-0001', + assetName: '联想台式机', + status: 'in_use' + } + ], + total: 1, + page: 1, + pageSize: 20 + } + + vi.mocked(assetApi.getAssetList).mockResolvedValue(mockData) + + const { fetchAssets, assets, loading } = useAsset() + await fetchAssets({ page: 1, page_size: 20 }) + + expect(loading.value).toBe(false) + expect(assets.value).toEqual(mockData.items) + expect(assetApi.getAssetList).toHaveBeenCalledWith({ page: 1, page_size: 20 }) + }) + + it('应该处理获取资产列表失败', async () => { + vi.mocked(assetApi.getAssetList).mockRejectedValue(new Error('网络错误')) + + const { fetchAssets, loading } = useAsset() + + await expect(fetchAssets({ page: 1 })).rejects.toThrow('网络错误') + expect(loading.value).toBe(false) + }) + + it('应该支持搜索参数', async () => { + vi.mocked(assetApi.getAssetList).mockResolvedValue({ items: [], total: 0 }) + + const { fetchAssets } = useAsset() + await fetchAssets({ + page: 1, + page_size: 20, + keyword: '联想', + status: 'in_use' + }) + + expect(assetApi.getAssetList).toHaveBeenCalledWith({ + page: 1, + page_size: 20, + keyword: '联想', + status: 'in_use' + }) + }) + }) + + describe('fetchAssetById', () => { + it('应该成功获取资产详情', async () => { + const mockAsset = { + id: 1, + assetCode: 'ASSET-20250124-0001', + assetName: '联想台式机', + status: 'in_use' + } + + vi.mocked(assetApi.getAssetById).mockResolvedValue(mockAsset) + + const { fetchAssetById, loading } = useAsset() + const result = await fetchAssetById(1) + + expect(loading.value).toBe(false) + expect(result).toEqual(mockAsset) + expect(assetApi.getAssetById).toHaveBeenCalledWith(1) + }) + + it('应该处理资产不存在的情况', async () => { + vi.mocked(assetApi.getAssetById).mockRejectedValue(new Error('资产不存在')) + + const { fetchAssetById } = useAsset() + + await expect(fetchAssetById(999)).rejects.toThrow('资产不存在') + }) + }) + + describe('createAsset', () => { + it('应该成功创建资产', async () => { + const newAsset = { + assetName: '新资产', + deviceTypeId: 1, + organizationId: 1 + } + + const createdAsset = { + id: 1, + assetCode: 'ASSET-20250124-0001', + ...newAsset + } + + vi.mocked(assetApi.createAsset).mockResolvedValue(createdAsset) + + const { createAsset: create, loading } = useAsset() + const result = await create(newAsset) + + expect(loading.value).toBe(false) + expect(result).toEqual(createdAsset) + expect(assetApi.createAsset).toHaveBeenCalledWith(newAsset) + }) + + it('应该处理创建失败', async () => { + const newAsset = { + assetName: '新资产', + deviceTypeId: 1, + organizationId: 1 + } + + vi.mocked(assetApi.createAsset).mockRejectedValue(new Error('创建失败')) + + const { createAsset: create } = useAsset() + + await expect(create(newAsset)).rejects.toThrow('创建失败') + }) + + it('应该验证必填字段', async () => { + const invalidAsset = { + assetName: '', // 空名称 + deviceTypeId: 0, // 无效ID + organizationId: 1 + } + + const { createAsset: create } = useAsset() + + await expect(create(invalidAsset)).rejects.toThrow() + }) + }) + + describe('updateAsset', () => { + it('应该成功更新资产', async () => { + const updateData = { + assetName: '更新后的名称', + location: '新位置' + } + + const updatedAsset = { + id: 1, + assetCode: 'ASSET-20250124-0001', + ...updateData + } + + vi.mocked(assetApi.updateAsset).mockResolvedValue(updatedAsset) + + const { updateAsset: update, loading } = useAsset() + const result = await update(1, updateData) + + expect(loading.value).toBe(false) + expect(result).toEqual(updatedAsset) + expect(assetApi.updateAsset).toHaveBeenCalledWith(1, updateData) + }) + + it('应该处理更新不存在的资产', async () => { + vi.mocked(assetApi.updateAsset).mockRejectedValue(new Error('资产不存在')) + + const { updateAsset: update } = useAsset() + + await expect(update(999, { assetName: '新名称' })).rejects.toThrow('资产不存在') + }) + }) + + describe('deleteAsset', () => { + it('应该成功删除资产', async () => { + vi.mocked(assetApi.deleteAsset).mockResolvedValue({}) + + const { deleteAsset: deleteFn, loading } = useAsset() + await deleteFn(1) + + expect(loading.value).toBe(false) + expect(assetApi.deleteAsset).toHaveBeenCalledWith(1) + }) + + it('应该处理删除失败', async () => { + vi.mocked(assetApi.deleteAsset).mockRejectedValue(new Error('删除失败')) + + const { deleteAsset: deleteFn } = useAsset() + + await expect(deleteFn(1)).rejects.toThrow('删除失败') + }) + + it('应该禁止删除使用中的资产', async () => { + vi.mocked(assetApi.deleteAsset).mockRejectedValue( + new Error('使用中的资产不能删除') + ) + + const { deleteAsset: deleteFn } = useAsset() + + await expect(deleteFn(1)).rejects.toThrow('使用中的资产不能删除') + }) + }) + + describe('importAssets', () => { + it('应该成功导入资产', async () => { + const mockFile = new File([''], 'test.xlsx', { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }) + + const importResult = { + total: 100, + success: 98, + failed: 2, + errors: [ + { row: 5, message: '设备类型不存在' }, + { row: 12, message: '序列号重复' } + ] + } + + vi.mocked(assetApi.importAssets).mockResolvedValue(importResult) + + const { importAssets: importFn, loading } = useAsset() + const result = await importFn(mockFile) + + expect(loading.value).toBe(false) + expect(result).toEqual(importResult) + expect(result.success).toBe(98) + expect(result.failed).toBe(2) + }) + + it('应该处理导入失败', async () => { + const mockFile = new File([''], 'test.xlsx') + + vi.mocked(assetApi.importAssets).mockRejectedValue(new Error('文件格式错误')) + + const { importAssets: importFn } = useAsset() + + await expect(importFn(mockFile)).rejects.toThrow('文件格式错误') + }) + }) + + describe('状态管理', () => { + it('应该正确设置loading状态', async () => { + vi.mocked(assetApi.getAssetList).mockImplementation(() => + new Promise(resolve => { + setTimeout(() => { + resolve({ items: [], total: 0 }) + }, 100) + }) + ) + + const { fetchAssets, loading } = useAsset() + + const promise = fetchAssets({ page: 1 }) + expect(loading.value).toBe(true) + + await promise + expect(loading.value).toBe(false) + }) + + it('应该管理assets响应式数据', async () => { + const mockData = { + items: [ + { id: 1, assetName: '资产1' }, + { id: 2, assetName: '资产2' } + ], + total: 2 + } + + vi.mocked(assetApi.getAssetList).mockResolvedValue(mockData) + + const { fetchAssets, assets } = useAsset() + await fetchAssets({ page: 1 }) + + expect(assets.value.length).toBe(2) + expect(assets.value[0].assetName).toBe('资产1') + }) + }) +}) diff --git a/tests/unit/composables/useECharts.test.ts b/tests/unit/composables/useECharts.test.ts new file mode 100644 index 0000000..baa2ac7 --- /dev/null +++ b/tests/unit/composables/useECharts.test.ts @@ -0,0 +1,141 @@ +/** + * useECharts Composable 测试 + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { ref } from 'vue' +import { useECharts } from '@/composables/useECharts' + +// Mock echarts +vi.mock('echarts', () => ({ + default: { + init: vi.fn(() => ({ + setOption: vi.fn(), + resize: vi.fn(), + dispose: vi.fn(), + on: vi.fn(), + off: vi.fn(), + showLoading: vi.fn(), + hideLoading: vi.fn(), + clear: vi.fn(), + getDataURL: vi.fn(), + })), + }, +})) + +describe('useECharts', () => { + let chartRef: Ref + + beforeEach(() => { + // 创建 mock DOM 元素 + const mockElement = document.createElement('div') + chartRef = ref(mockElement) + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it('initializes chart correctly', () => { + const { chart, isReady } = useECharts(chartRef) + + // 检查图表实例是否创建 + expect(chart.value).toBeTruthy() + }) + + it('sets chart option', () => { + const { setOption } = useECharts(chartRef) + + const option = { + series: [{ + type: 'pie', + data: [{ name: '测试', value: 100 }], + }], + } + + setOption(option) + + // 验证 setOption 被调用 + expect(chart.value?.setOption).toHaveBeenCalled() + }) + + it('shows loading', () => { + const { showLoading, loading } = useECharts(chartRef) + + showLoading({ text: '加载中...' }) + + expect(loading.value).toBe(true) + expect(chart.value?.showLoading).toHaveBeenCalled() + }) + + it('hides loading', () => { + const { hideLoading, loading } = useECharts(chartRef) + + hideLoading() + + expect(loading.value).toBe(false) + expect(chart.value?.hideLoading).toHaveBeenCalled() + }) + + it('resizes chart', () => { + const { resize } = useECharts(chartRef) + + resize() + + expect(chart.value?.resize).toHaveBeenCalled() + }) + + it('disposes chart', () => { + const { dispose, chart } = useECharts(chartRef) + + dispose() + + expect(chart.value?.dispose).toHaveBeenCalled() + }) + + it('clears chart', () => { + const { clear } = useECharts(chartRef) + + clear() + + expect(chart.value?.clear).toHaveBeenCalled() + }) + + it('binds event', () => { + const { on } = useECharts(chartRef) + + const handler = vi.fn() + on('click', handler) + + expect(chart.value?.on).toHaveBeenCalledWith('click', handler) + }) + + it('unbinds event', () => { + const { off } = useECharts(chartRef) + + const handler = vi.fn() + off('click', handler) + + expect(chart.value?.off).toHaveBeenCalledWith('click', handler) + }) + + it('gets data URL', () => { + const { getDataURL } = useECharts(chartRef) + + getDataURL({ type: 'png', pixelRatio: 2 }) + + expect(chart.value?.getDataURL).toHaveBeenCalledWith({ + type: 'png', + pixelRatio: 2, + backgroundColor: '#fff', + }) + }) + + it('returns chart instance', () => { + const { getInstance } = useECharts(chartRef) + + const instance = getInstance() + + expect(instance).toBeTruthy() + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..16521e6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path mapping */ + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/verify-charts.sh b/verify-charts.sh new file mode 100644 index 0000000..5967b1a --- /dev/null +++ b/verify-charts.sh @@ -0,0 +1,164 @@ +#!/bin/bash + +# 图表组件库验证脚本 + +echo "==================================" +echo "图表组件库验证" +echo "==================================" +echo "" + +# 检查核心组件 +echo "1. 检查核心组件..." +files=( + "src/components/charts/BaseChart.vue" + "src/components/charts/PieChart.vue" + "src/components/charts/BarChart.vue" + "src/components/charts/LineChart.vue" + "src/components/charts/GaugeChart.vue" + "src/components/charts/FunnelChart.vue" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "2. 检查业务图表组件..." +files=( + "src/components/charts/business/AssetStatusChart.vue" + "src/components/charts/business/AssetDistributionChart.vue" + "src/components/charts/business/AssetValueTrendChart.vue" + "src/components/charts/business/AssetUtilizationChart.vue" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "3. 检查统计卡片组件..." +files=( + "src/components/statistics/StatCard.vue" + "src/components/statistics/StatCardGroup.vue" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "4. 检查 Composables..." +files=( + "src/composables/useECharts.ts" + "src/composables/useChartData.ts" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "5. 检查工具函数..." +files=( + "src/utils/echarts.ts" + "src/utils/echarts/performance.ts" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "6. 检查类型定义..." +files=( + "src/types/charts.ts" + "src/components/charts/charts.d.ts" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "7. 检查文档..." +files=( + "CHARTS_README.md" + "CHARTS_QUICKSTART.md" + "CHARTS_DELIVERY.md" + "CHARTS_SUMMARY.md" + "CHARTES_START_HERE.md" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "8. 检查示例页面..." +files=( + "src/views/examples/ChartsExample.vue" + "tests/unit/components/PieChart.test.ts" + "tests/unit/composables/useECharts.test.ts" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "9. 检查导出文件..." +files=( + "src/components/charts/index.ts" + "src/components/statistics/index.ts" +) + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo " ✅ $file" + else + echo " ❌ $file (缺失)" + fi +done + +echo "" +echo "==================================" +echo "验证完成!" +echo "==================================" +echo "" +echo "下一步:" +echo "1. 运行项目:npm run dev" +echo "2. 访问示例:http://localhost:5173/examples/charts" +echo "3. 查看文档:CHARTES_START_HERE.md" +echo "" diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..6f76fc5 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,72 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +// https://vitejs.dev/config/ +export default defineConfig({ + css: { + preprocessorOptions: { + scss: { + additionalData: `@use "@/assets/styles/variables.scss" as *;` + } + } + }, + plugins: [ + vue(), + AutoImport({ + // 自动导入 Vue 相关函数 + imports: [ + 'vue', + 'vue-router', + 'pinia', + '@vueuse/core' + ], + // 自动导入 Element Plus 相关函数 + resolvers: [ElementPlusResolver()], + dts: 'src/auto-imports.d.ts', + eslintrc: { + enabled: true + } + }), + Components({ + // 自动导入 Element Plus 组件 + resolvers: [ElementPlusResolver()], + dts: 'src/components.d.ts' + }) + ], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + server: { + port: 3000, + open: true, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '/api/v1') + } + } + }, + build: { + target: 'es2015', + outDir: 'dist', + assetsDir: 'assets', + sourcemap: false, + chunkSizeWarningLimit: 1500, + rollupOptions: { + output: { + manualChunks: { + 'vue-vendor': ['vue', 'vue-router', 'pinia'], + 'element-plus': ['element-plus', '@element-plus/icons-vue'], + 'echarts': ['echarts'] + } + } + } + } +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..da4ed05 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,105 @@ +/// + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + test: { + // 测试环境 + environment: 'jsdom', + + // 全局配置 + globals: true, + + // 设置超时时间 + testTimeout: 10000, + hookTimeout: 10000, + + // 覆盖率配置 + coverage: { + // 提供器 + provider: 'v8', + + // 覆盖率报告目录 + reportsDirectory: './test_reports/coverage', + + // 覆盖率报告格式 + reporter: [ + 'text', + 'json', + 'html', + 'lcov', + 'lcovonly' + ], + + // 覆盖率阈值 + lines: 70, + functions: 70, + branches: 70, + statements: 70, + + // 包含的文件 + include: [ + 'src/**/*.{js,ts,vue}', + '!src/main.ts', + '!src/**/*.d.ts' + ], + + // 排除的文件 + exclude: [ + 'node_modules/', + 'tests/', + '**/*.spec.ts', + '**/*.test.ts', + '**/types/', + '**/router/', + '**/main.ts' + ] + }, + + // 测试文件匹配模式 + include: [ + 'tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + 'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}' + ], + + // 排除的文件 + exclude: [ + 'node_modules', + 'dist', + '.idea', + '.git', + '.cache' + ], + + // 全局setup文件 + setupFiles: ['./tests/setup.ts'], + + // 监听模式配置 + watch: false, + + // 并行执行 + threads: true, + maxThreads: 4, + minThreads: 1, + + // 隔穿环境 + isolate: true, + + // 报告器 + reporters: ['verbose', 'json', 'html'], + + // 输出目录 + outputFile: { + json: './test_reports/vitest-results.json', + html: './test_reports/vitest-report.html' + } + } +})