commit 8c1a09f0759ccb8dc65b4922b76644a328016bea Author: liangzai <2440983361@qq.com> Date: Wed May 27 16:57:43 2026 +0800 f diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5a5809d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +end_of_line = lf +max_line_length = 100 diff --git a/.env b/.env new file mode 100644 index 0000000..b00ceb8 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +VITE_API_URL="https://api.haiyudata.com" +VITE_CAPTCHA_SCENE_ID="wynt39to" +VITE_CAPTCHA_ENCRYPTED_MODE=false diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json new file mode 100644 index 0000000..341e2f9 --- /dev/null +++ b/.eslintrc-auto-import.json @@ -0,0 +1,418 @@ +{ + "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, + "DirectiveBinding": true, + "EffectScope": true, + "ExtractDefaultPropTypes": true, + "ExtractPropTypes": true, + "ExtractPublicPropTypes": true, + "InjectionKey": true, + "MaybeRef": true, + "MaybeRefOrGetter": true, + "PERMISSIONS": true, + "PropType": true, + "ROLES": true, + "ROLE_PERMISSIONS": true, + "Ref": true, + "Slot": true, + "Slots": true, + "VERSION_CONFIG": true, + "VNode": true, + "VersionChecker": true, + "WritableComputedRef": true, + "acceptHMRUpdate": true, + "applyRenderOptimizations": true, + "asyncComputed": true, + "authEventBus": true, + "autoResetRef": true, + "axios": true, + "buildTocFromMarkdown": true, + "camelCase": true, + "capitalize": true, + "checkForUpdates": true, + "checkPermission": true, + "clearLocalVersions": true, + "compareVersions": true, + "computed": true, + "computedAsync": true, + "computedEager": true, + "computedInject": true, + "computedWithControl": true, + "controlledComputed": true, + "controlledRef": true, + "copyToClipboard": true, + "createApp": true, + "createEventHook": true, + "createGlobalState": true, + "createInjectionState": true, + "createIntersectionObserver": true, + "createPinia": true, + "createReactiveFn": true, + "createRef": true, + "createReusableTemplate": true, + "createSharedComposable": true, + "createTemplatePromise": true, + "createUnrefFn": true, + "customRef": true, + "dayjs": true, + "debounce": true, + "debouncedRef": true, + "debouncedWatch": true, + "deepClone": true, + "defineAsyncComponent": true, + "defineComponent": true, + "defineStore": true, + "downloadFile": true, + "eagerComputed": true, + "effectScope": true, + "encodeRequest": true, + "endsWith": true, + "escape": true, + "extendRef": true, + "filter": true, + "find": true, + "findIndex": true, + "formatDate": true, + "formatFileSize": true, + "formatMoney": true, + "formatPhone": true, + "fromNow": true, + "generateNonce": true, + "generateSMSRequest": true, + "generateUUID": true, + "get": true, + "getActivePinia": true, + "getBrowserInfo": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "getDevicePerformanceLevel": true, + "getLocalVersions": true, + "getUrlParam": true, + "getUserPermissions": true, + "groupBy": true, + "h": true, + "handleError": true, + "handleResponse": true, + "hasAllPermissions": true, + "hasAnyPermission": true, + "hasUserPermission": true, + "ignorableWatch": true, + "includes": true, + "initRenderOptimizations": true, + "inject": true, + "injectLocal": true, + "isAlipay": true, + "isDefined": true, + "isEmpty": true, + "isEqual": true, + "isMobile": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "isWeChat": true, + "kebabCase": true, + "keyBy": true, + "lowerCase": true, + "makeDestructurable": true, + "map": true, + "mapActions": true, + "mapGetters": true, + "mapState": true, + "mapStores": true, + "mapWritableState": true, + "markRaw": true, + "merge": true, + "nextTick": true, + "omit": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeRouteLeave": true, + "onBeforeRouteUpdate": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onClickOutside": true, + "onDeactivated": true, + "onElementRemoval": 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, + "optimizeAnimations": true, + "optimizeImageLoading": true, + "optimizeLayout": true, + "optimizeMemory": true, + "orderBy": true, + "pausableWatch": true, + "permission": true, + "pick": true, + "prefersReducedMotion": true, + "provide": true, + "provideLocal": true, + "reactify": true, + "reactifyObject": true, + "reactive": true, + "reactiveComputed": true, + "reactiveOmit": true, + "reactivePick": true, + "readonly": true, + "reduce": true, + "ref": true, + "refAutoReset": true, + "refDebounced": true, + "refDefault": true, + "refThrottled": true, + "refWithControl": true, + "removeUrlParam": true, + "renderLegalMarkdown": true, + "request": true, + "resolveComponent": true, + "resolveRef": true, + "resolveUnref": true, + "role": true, + "saveLocalVersions": true, + "set": true, + "setActivePinia": true, + "setMapStoreSuffix": true, + "setUrlParam": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "snakeCase": true, + "sortBy": true, + "startsWith": true, + "storeToRefs": true, + "supportsBackdropFilter": true, + "supportsHardwareAcceleration": true, + "syncRef": true, + "syncRefs": true, + "templateRef": true, + "throttle": true, + "throttledRef": true, + "throttledWatch": true, + "toRaw": true, + "toReactive": true, + "toRef": true, + "toRefs": true, + "toValue": true, + "triggerRef": true, + "trim": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unescape": true, + "uniq": true, + "uniqBy": true, + "unref": true, + "unrefElement": true, + "until": true, + "upperCase": true, + "useActiveElement": true, + "useAliyunCaptcha": true, + "useAnimate": true, + "useAppStore": 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, + "useCertification": true, + "useClipboard": true, + "useClipboardItems": true, + "useCloned": true, + "useColorMode": true, + "useConfirmDialog": true, + "useCountdown": true, + "useCounter": true, + "useCounterStore": 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, + "useMobileTable": 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, + "usePreferredReducedTransparency": true, + "usePrevious": true, + "useRafFn": true, + "useRefHistory": true, + "useResizeObserver": true, + "useRoute": true, + "useRouter": true, + "useSSRWidth": 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, + "useTimeAgoIntl": true, + "useTimeout": true, + "useTimeoutFn": true, + "useTimeoutPoll": true, + "useTimestamp": true, + "useTitle": true, + "useToNumber": true, + "useToString": true, + "useToggle": true, + "useTransition": true, + "useUrlSearchParams": true, + "useUserMedia": true, + "useUserStore": 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, + "validateEmail": true, + "validateIdCard": true, + "validatePhone": true, + "version": true, + "versionChecker": 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/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..29a2402 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "singleQuote": true, + "printWidth": 100 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..3f84126 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "Vue.volar", + "dbaeumer.vscode-eslint", + "EditorConfig.EditorConfig", + "oxc.oxc-vscode", + "esbenp.prettier-vscode" + ] +} diff --git a/auto-imports.d.ts b/auto-imports.d.ts new file mode 100644 index 0000000..5e8af86 --- /dev/null +++ b/auto-imports.d.ts @@ -0,0 +1,871 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const ElLoading: typeof import('element-plus/es')['ElLoading'] + const ElMessage: typeof import('element-plus/es')['ElMessage'] + const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] + const ElNotification: typeof import('element-plus')['ElNotification'] + const PERMISSIONS: typeof import('./src/utils/permission.js')['PERMISSIONS'] + const ROLES: typeof import('./src/utils/permission.js')['ROLES'] + const ROLE_PERMISSIONS: typeof import('./src/utils/permission.js')['ROLE_PERMISSIONS'] + const VERSION_CONFIG: typeof import('./src/utils/version.js')['VERSION_CONFIG'] + const VersionChecker: typeof import('./src/utils/version.js')['VersionChecker'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const applyPerformanceOptimizations: typeof import('./src/utils/performance.js')['applyPerformanceOptimizations'] + const applyRenderOptimizations: typeof import('./src/utils/performance.js')['applyRenderOptimizations'] + const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] + const authEventBus: typeof import('./src/utils/request.js')['authEventBus'] + const autoImports: typeof import('./src/utils/auto-imports.js')['autoImports'] + const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] + const axios: typeof import('axios')['default'] + const buildTocFromMarkdown: typeof import('./src/utils/legalMarkdown.js')['buildTocFromMarkdown'] + const camelCase: typeof import('lodash-es')['camelCase'] + const capitalize: typeof import('lodash-es')['capitalize'] + const checkForUpdates: typeof import('./src/utils/version.js')['checkForUpdates'] + const checkPermission: typeof import('./src/utils/permission.js')['checkPermission'] + const clearLocalVersions: typeof import('./src/utils/version.js')['clearLocalVersions'] + const cloneDeep: typeof import('lodash-es')['cloneDeep'] + const compareVersions: typeof import('./src/utils/version.js')['compareVersions'] + 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 copyToClipboard: typeof import('./src/utils/index.js')['copyToClipboard'] + 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 createIntersectionObserver: typeof import('./src/utils/performance.js')['createIntersectionObserver'] + const createPinia: typeof import('pinia')['createPinia'] + const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] + const createRef: typeof import('@vueuse/core')['createRef'] + const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] + const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] + const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] + const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] + const customRef: typeof import('vue')['customRef'] + const dayjs: typeof import('dayjs')['default'] + const debounce: typeof import('./src/utils/performance.js')['debounce'] + const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] + const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] + const deepClone: typeof import('./src/utils/index.js')['deepClone'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const downloadFile: typeof import('./src/utils/index.js')['downloadFile'] + const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] + const effectScope: typeof import('vue')['effectScope'] + const encodeRequest: typeof import('./src/utils/smsSignature.js')['encodeRequest'] + const endsWith: typeof import('lodash-es')['endsWith'] + const errorMonitor: typeof import('./src/utils/errorMonitor.js')['default'] + const escape: typeof import('lodash-es')['escape'] + const exportToCSV: typeof import('./src/utils/export.js')['exportToCSV'] + const exportToExcel: typeof import('./src/utils/export.js')['exportToExcel'] + const extendRef: typeof import('@vueuse/core')['extendRef'] + const filter: typeof import('lodash-es')['filter'] + const find: typeof import('lodash-es')['find'] + const findIndex: typeof import('lodash-es')['findIndex'] + const formatAmount: typeof import('./src/utils/export.js')['formatAmount'] + const formatDate: typeof import('./src/utils/index.js')['formatDate'] + const formatDateTime: typeof import('./src/utils/export.js')['formatDateTime'] + const formatFileSize: typeof import('./src/utils/index.js')['formatFileSize'] + const formatMoney: typeof import('./src/utils/index.js')['formatMoney'] + const formatPhone: typeof import('./src/utils/index.js')['formatPhone'] + const fromNow: typeof import('./src/utils/index.js')['fromNow'] + const generateFilename: typeof import('./src/utils/export.js')['generateFilename'] + const generateNonce: typeof import('./src/utils/smsSignature.js')['generateNonce'] + const generateSMSRequest: typeof import('./src/utils/smsSignature.js')['generateSMSRequest'] + const generateUUID: typeof import('./src/utils/index.js')['generateUUID'] + const get: typeof import('lodash-es')['get'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getBrowserInfo: typeof import('./src/utils/index.js')['getBrowserInfo'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const getDevicePerformanceLevel: typeof import('./src/utils/performance.js')['getDevicePerformanceLevel'] + const getLocalVersions: typeof import('./src/utils/version.js')['getLocalVersions'] + const getNetworkQuality: typeof import('./src/utils/performance.js')['getNetworkQuality'] + const getUrlParam: typeof import('./src/utils/index.js')['getUrlParam'] + const getUserPermissions: typeof import('./src/utils/permission.js')['getUserPermissions'] + const groupBy: typeof import('lodash-es')['groupBy'] + const h: typeof import('vue')['h'] + const handleError: typeof import('./src/utils/request.js')['handleError'] + const handleResponse: typeof import('./src/utils/request.js')['handleResponse'] + const hasAllPermissions: typeof import('./src/utils/permission.js')['hasAllPermissions'] + const hasAnyPermission: typeof import('./src/utils/permission.js')['hasAnyPermission'] + const hasUserPermission: typeof import('./src/utils/permission.js')['hasUserPermission'] + const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] + const includes: typeof import('lodash-es')['includes'] + const initPerformanceOptimizations: typeof import('./src/utils/performance.js')['initPerformanceOptimizations'] + const initRenderOptimizations: typeof import('./src/utils/performance.js')['initRenderOptimizations'] + const inject: typeof import('vue')['inject'] + const injectLocal: typeof import('@vueuse/core')['injectLocal'] + const isAlipay: typeof import('./src/utils/index.js')['isAlipay'] + const isDefined: typeof import('@vueuse/core')['isDefined'] + const isEmpty: typeof import('lodash-es')['isEmpty'] + const isEqual: typeof import('lodash-es')['isEqual'] + const isMobile: typeof import('./src/utils/index.js')['isMobile'] + 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 isWeChat: typeof import('./src/utils/index.js')['isWeChat'] + const kebabCase: typeof import('lodash-es')['kebabCase'] + const keyBy: typeof import('lodash-es')['keyBy'] + const lowerCase: typeof import('lodash-es')['lowerCase'] + const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] + const map: typeof import('lodash-es')['map'] + 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 merge: typeof import('lodash-es')['merge'] + const nextTick: typeof import('vue')['nextTick'] + const omit: typeof import('lodash-es')['omit'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] + const onLongPress: typeof import('@vueuse/core')['onLongPress'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const optimizeAnimations: typeof import('./src/utils/performance.js')['optimizeAnimations'] + const optimizeImageLoading: typeof import('./src/utils/performance.js')['optimizeImageLoading'] + const optimizeLayout: typeof import('./src/utils/performance.js')['optimizeLayout'] + const optimizeMemory: typeof import('./src/utils/performance.js')['optimizeMemory'] + const optimizeNetwork: typeof import('./src/utils/performance.js')['optimizeNetwork'] + const optimizeTransitions: typeof import('./src/utils/performance.js')['optimizeTransitions'] + const orderBy: typeof import('lodash-es')['orderBy'] + const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] + const permission: typeof import('./src/utils/permission.js')['default'] + const pick: typeof import('lodash-es')['pick'] + const prefersReducedMotion: typeof import('./src/utils/performance.js')['prefersReducedMotion'] + 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 reduce: typeof import('lodash-es')['reduce'] + 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 removeUrlParam: typeof import('./src/utils/index.js')['removeUrlParam'] + const renderLegalMarkdown: typeof import('./src/utils/legalMarkdown.js')['renderLegalMarkdown'] + const request: typeof import('./src/utils/request.js')['default'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const resolveRef: typeof import('@vueuse/core')['resolveRef'] + const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] + const role: typeof import('./src/utils/permission.js')['role'] + const saveLocalVersions: typeof import('./src/utils/version.js')['saveLocalVersions'] + const set: typeof import('lodash-es')['set'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const setUrlParam: typeof import('./src/utils/index.js')['setUrlParam'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const snakeCase: typeof import('lodash-es')['snakeCase'] + const sortBy: typeof import('lodash-es')['sortBy'] + const startsWith: typeof import('lodash-es')['startsWith'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const supportsBackdropFilter: typeof import('./src/utils/performance.js')['supportsBackdropFilter'] + const supportsHardwareAcceleration: typeof import('./src/utils/performance.js')['supportsHardwareAcceleration'] + const syncRef: typeof import('@vueuse/core')['syncRef'] + const syncRefs: typeof import('@vueuse/core')['syncRefs'] + const templateRef: typeof import('@vueuse/core')['templateRef'] + const throttle: typeof import('./src/utils/performance.js')['throttle'] + 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 trim: typeof import('lodash-es')['trim'] + 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 unescape: typeof import('lodash-es')['unescape'] + const uniq: typeof import('lodash-es')['uniq'] + const uniqBy: typeof import('lodash-es')['uniqBy'] + const unref: typeof import('vue')['unref'] + const unrefElement: typeof import('@vueuse/core')['unrefElement'] + const until: typeof import('@vueuse/core')['until'] + const upperCase: typeof import('lodash-es')['upperCase'] + const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] + const useAliyunCaptcha: typeof import('./src/composables/useAliyunCaptcha.js')['default'] + const useAnimate: typeof import('@vueuse/core')['useAnimate'] + const useAppStore: typeof import('./src/stores/app.js')['useAppStore'] + 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 useAsyncData: typeof import('@vueuse/core')['useAsyncData'] + 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 useBluetoothAvailability: typeof import('@vueuse/core')['useBluetoothAvailability'] + const useBluetoothConnect: typeof import('@vueuse/core')['useBluetoothConnect'] + const useBluetoothDisconnect: typeof import('@vueuse/core')['useBluetoothDisconnect'] + const useBluetoothGetAvailability: typeof import('@vueuse/core')['useBluetoothGetAvailability'] + const useBluetoothGetCharacteristic: typeof import('@vueuse/core')['useBluetoothGetCharacteristic'] + const useBluetoothGetCharacteristics: typeof import('@vueuse/core')['useBluetoothGetCharacteristics'] + const useBluetoothGetDescriptor: typeof import('@vueuse/core')['useBluetoothGetDescriptor'] + const useBluetoothGetDescriptors: typeof import('@vueuse/core')['useBluetoothGetDescriptors'] + const useBluetoothGetDevices: typeof import('@vueuse/core')['useBluetoothGetDevices'] + const useBluetoothGetService: typeof import('@vueuse/core')['useBluetoothGetService'] + const useBluetoothGetServices: typeof import('@vueuse/core')['useBluetoothGetServices'] + const useBluetoothReadValue: typeof import('@vueuse/core')['useBluetoothReadValue'] + const useBluetoothRequestDevice: typeof import('@vueuse/core')['useBluetoothRequestDevice'] + const useBluetoothStartNotifications: typeof import('@vueuse/core')['useBluetoothStartNotifications'] + const useBluetoothStopNotifications: typeof import('@vueuse/core')['useBluetoothStopNotifications'] + const useBluetoothWriteValue: typeof import('@vueuse/core')['useBluetoothWriteValue'] + 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 useCertification: typeof import('./src/composables/useCertification.js')['useCertification'] + const useClickOutside: typeof import('@vueuse/core')['useClickOutside'] + const useClipboard: typeof import('@vueuse/core')['useClipboard'] + const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] + const useCloned: typeof import('@vueuse/core')['useCloned'] + const useColorMode: typeof import('@vueuse/core')['useColorMode'] + const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] + const useCountdown: typeof import('@vueuse/core')['useCountdown'] + const useCounter: typeof import('@vueuse/core')['useCounter'] + const useCounterStore: typeof import('./src/stores/counter.js')['useCounterStore'] + 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 useDoubleClick: typeof import('@vueuse/core')['useDoubleClick'] + const useDraggable: typeof import('@vueuse/core')['useDraggable'] + const useDropZone: typeof import('@vueuse/core')['useDropZone'] + const useDroppable: typeof import('@vueuse/core')['useDroppable'] + const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] + const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] + const useElementFocus: typeof import('@vueuse/core')['useElementFocus'] + 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 useGesture: typeof import('@vueuse/core')['useGesture'] + const useHotkeys: typeof import('@vueuse/core')['useHotkeys'] + 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 useKeyCombo: typeof import('@vueuse/core')['useKeyCombo'] + const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] + const useKeyPressed: typeof import('@vueuse/core')['useKeyPressed'] + const useKeyboard: typeof import('@vueuse/core')['useKeyboard'] + const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] + const useLink: typeof import('vue-router')['useLink'] + const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] + const useLongPress: typeof import('@vueuse/core')['useLongPress'] + 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 useMobileTable: typeof import('./src/composables/useMobileTable.js')['useMobileTable'] + 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 usePageVisibility: typeof import('@vueuse/core')['usePageVisibility'] + const useParallax: typeof import('@vueuse/core')['useParallax'] + const useParentElement: typeof import('@vueuse/core')['useParentElement'] + const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] + const usePermission: typeof import('@vueuse/core')['usePermission'] + const usePointer: typeof import('@vueuse/core')['usePointer'] + const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] + const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] + const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] + const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] + const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] + const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] + const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] + const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency'] + const usePrevious: typeof import('@vueuse/core')['usePrevious'] + const useRafFn: typeof import('@vueuse/core')['useRafFn'] + const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] + const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth'] + const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] + const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] + const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] + const useScroll: typeof import('@vueuse/core')['useScroll'] + const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] + const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] + const useShare: typeof import('@vueuse/core')['useShare'] + const useSharedWorker: typeof import('@vueuse/core')['useSharedWorker'] + const useSlots: typeof import('vue')['useSlots'] + const useSortable: typeof import('@vueuse/core')['useSortable'] + const useSorted: typeof import('@vueuse/core')['useSorted'] + const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] + const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] + const useStepper: typeof import('@vueuse/core')['useStepper'] + const useStorage: typeof import('@vueuse/core')['useStorage'] + const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] + const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] + const useSupported: typeof import('@vueuse/core')['useSupported'] + const useSwipe: typeof import('@vueuse/core')['useSwipe'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] + const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] + const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] + const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] + const useThrottle: typeof import('@vueuse/core')['useThrottle'] + const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] + const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] + const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] + const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl'] + const useTimeout: typeof import('@vueuse/core')['useTimeout'] + const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] + const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] + const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] + const useTitle: typeof import('@vueuse/core')['useTitle'] + const useToNumber: typeof import('@vueuse/core')['useToNumber'] + const useToString: typeof import('@vueuse/core')['useToString'] + const useToggle: typeof import('@vueuse/core')['useToggle'] + const useToggleDark: typeof import('@vueuse/core')['useToggleDark'] + const useTransition: typeof import('@vueuse/core')['useTransition'] + const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] + const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] + const useUserStore: typeof import('./src/stores/user.js')['useUserStore'] + const useVModel: typeof import('@vueuse/core')['useVModel'] + const useVModels: typeof import('@vueuse/core')['useVModels'] + const useVersionStore: typeof import('./src/stores/version.js')['useVersionStore'] + 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 validateEmail: typeof import('./src/utils/index.js')['validateEmail'] + const validateIdCard: typeof import('./src/utils/index.js')['validateIdCard'] + const validatePhone: typeof import('./src/utils/index.js')['validatePhone'] + const version: typeof import('./src/utils/version.js')['default'] + const versionChecker: typeof import('./src/utils/version.js')['versionChecker'] + 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, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') + // @ts-ignore + export type { VersionChecker } from './src/utils/version.js' + import('./src/utils/version.js') +} + +// for vue template auto import +import { UnwrapRef } from 'vue' +declare module 'vue' { + interface GlobalComponents {} + interface ComponentCustomProperties { + readonly EffectScope: UnwrapRef + readonly PERMISSIONS: UnwrapRef + readonly ROLES: UnwrapRef + readonly ROLE_PERMISSIONS: UnwrapRef + readonly VERSION_CONFIG: UnwrapRef + readonly VersionChecker: UnwrapRef + readonly acceptHMRUpdate: UnwrapRef + readonly applyRenderOptimizations: UnwrapRef + readonly asyncComputed: UnwrapRef + readonly authEventBus: UnwrapRef + readonly autoResetRef: UnwrapRef + readonly axios: UnwrapRef + readonly buildTocFromMarkdown: UnwrapRef + readonly camelCase: UnwrapRef + readonly capitalize: UnwrapRef + readonly checkForUpdates: UnwrapRef + readonly checkPermission: UnwrapRef + readonly clearLocalVersions: UnwrapRef + readonly compareVersions: UnwrapRef + readonly computed: UnwrapRef + readonly computedAsync: UnwrapRef + readonly computedEager: UnwrapRef + readonly computedInject: UnwrapRef + readonly computedWithControl: UnwrapRef + readonly controlledComputed: UnwrapRef + readonly controlledRef: UnwrapRef + readonly copyToClipboard: UnwrapRef + readonly createApp: UnwrapRef + readonly createEventHook: UnwrapRef + readonly createGlobalState: UnwrapRef + readonly createInjectionState: UnwrapRef + readonly createIntersectionObserver: UnwrapRef + readonly createPinia: UnwrapRef + readonly createReactiveFn: UnwrapRef + readonly createRef: UnwrapRef + readonly createReusableTemplate: UnwrapRef + readonly createSharedComposable: UnwrapRef + readonly createTemplatePromise: UnwrapRef + readonly createUnrefFn: UnwrapRef + readonly customRef: UnwrapRef + readonly dayjs: UnwrapRef + readonly debounce: UnwrapRef + readonly debouncedRef: UnwrapRef + readonly debouncedWatch: UnwrapRef + readonly deepClone: UnwrapRef + readonly defineAsyncComponent: UnwrapRef + readonly defineComponent: UnwrapRef + readonly defineStore: UnwrapRef + readonly downloadFile: UnwrapRef + readonly eagerComputed: UnwrapRef + readonly effectScope: UnwrapRef + readonly encodeRequest: UnwrapRef + readonly endsWith: UnwrapRef + readonly escape: UnwrapRef + readonly extendRef: UnwrapRef + readonly filter: UnwrapRef + readonly find: UnwrapRef + readonly findIndex: UnwrapRef + readonly formatDate: UnwrapRef + readonly formatFileSize: UnwrapRef + readonly formatMoney: UnwrapRef + readonly formatPhone: UnwrapRef + readonly fromNow: UnwrapRef + readonly generateNonce: UnwrapRef + readonly generateSMSRequest: UnwrapRef + readonly generateUUID: UnwrapRef + readonly get: UnwrapRef + readonly getActivePinia: UnwrapRef + readonly getBrowserInfo: UnwrapRef + readonly getCurrentInstance: UnwrapRef + readonly getCurrentScope: UnwrapRef + readonly getDevicePerformanceLevel: UnwrapRef + readonly getLocalVersions: UnwrapRef + readonly getUrlParam: UnwrapRef + readonly getUserPermissions: UnwrapRef + readonly groupBy: UnwrapRef + readonly h: UnwrapRef + readonly handleError: UnwrapRef + readonly handleResponse: UnwrapRef + readonly hasAllPermissions: UnwrapRef + readonly hasAnyPermission: UnwrapRef + readonly hasUserPermission: UnwrapRef + readonly ignorableWatch: UnwrapRef + readonly includes: UnwrapRef + readonly initRenderOptimizations: UnwrapRef + readonly inject: UnwrapRef + readonly injectLocal: UnwrapRef + readonly isAlipay: UnwrapRef + readonly isDefined: UnwrapRef + readonly isEmpty: UnwrapRef + readonly isEqual: UnwrapRef + readonly isMobile: UnwrapRef + readonly isProxy: UnwrapRef + readonly isReactive: UnwrapRef + readonly isReadonly: UnwrapRef + readonly isRef: UnwrapRef + readonly isWeChat: UnwrapRef + readonly kebabCase: UnwrapRef + readonly keyBy: UnwrapRef + readonly lowerCase: UnwrapRef + readonly makeDestructurable: UnwrapRef + readonly map: UnwrapRef + readonly mapActions: UnwrapRef + readonly mapGetters: UnwrapRef + readonly mapState: UnwrapRef + readonly mapStores: UnwrapRef + readonly mapWritableState: UnwrapRef + readonly markRaw: UnwrapRef + readonly merge: UnwrapRef + readonly nextTick: UnwrapRef + readonly omit: UnwrapRef + readonly onActivated: UnwrapRef + readonly onBeforeMount: UnwrapRef + readonly onBeforeRouteLeave: UnwrapRef + readonly onBeforeRouteUpdate: UnwrapRef + readonly onBeforeUnmount: UnwrapRef + readonly onBeforeUpdate: UnwrapRef + readonly onClickOutside: UnwrapRef + readonly onDeactivated: UnwrapRef + readonly onElementRemoval: UnwrapRef + readonly onErrorCaptured: UnwrapRef + readonly onKeyStroke: UnwrapRef + readonly onLongPress: UnwrapRef + readonly onMounted: UnwrapRef + readonly onRenderTracked: UnwrapRef + readonly onRenderTriggered: UnwrapRef + readonly onScopeDispose: UnwrapRef + readonly onServerPrefetch: UnwrapRef + readonly onStartTyping: UnwrapRef + readonly onUnmounted: UnwrapRef + readonly onUpdated: UnwrapRef + readonly onWatcherCleanup: UnwrapRef + readonly optimizeAnimations: UnwrapRef + readonly optimizeImageLoading: UnwrapRef + readonly optimizeLayout: UnwrapRef + readonly optimizeMemory: UnwrapRef + readonly orderBy: UnwrapRef + readonly pausableWatch: UnwrapRef + readonly permission: UnwrapRef + readonly pick: UnwrapRef + readonly prefersReducedMotion: UnwrapRef + readonly provide: UnwrapRef + readonly provideLocal: UnwrapRef + readonly reactify: UnwrapRef + readonly reactifyObject: UnwrapRef + readonly reactive: UnwrapRef + readonly reactiveComputed: UnwrapRef + readonly reactiveOmit: UnwrapRef + readonly reactivePick: UnwrapRef + readonly readonly: UnwrapRef + readonly reduce: UnwrapRef + readonly ref: UnwrapRef + readonly refAutoReset: UnwrapRef + readonly refDebounced: UnwrapRef + readonly refDefault: UnwrapRef + readonly refThrottled: UnwrapRef + readonly refWithControl: UnwrapRef + readonly removeUrlParam: UnwrapRef + readonly renderLegalMarkdown: UnwrapRef + readonly request: UnwrapRef + readonly resolveComponent: UnwrapRef + readonly resolveRef: UnwrapRef + readonly resolveUnref: UnwrapRef + readonly role: UnwrapRef + readonly saveLocalVersions: UnwrapRef + readonly set: UnwrapRef + readonly setActivePinia: UnwrapRef + readonly setMapStoreSuffix: UnwrapRef + readonly setUrlParam: UnwrapRef + readonly shallowReactive: UnwrapRef + readonly shallowReadonly: UnwrapRef + readonly shallowRef: UnwrapRef + readonly snakeCase: UnwrapRef + readonly sortBy: UnwrapRef + readonly startsWith: UnwrapRef + readonly storeToRefs: UnwrapRef + readonly supportsBackdropFilter: UnwrapRef + readonly supportsHardwareAcceleration: UnwrapRef + readonly syncRef: UnwrapRef + readonly syncRefs: UnwrapRef + readonly templateRef: UnwrapRef + readonly throttle: UnwrapRef + readonly throttledRef: UnwrapRef + readonly throttledWatch: UnwrapRef + readonly toRaw: UnwrapRef + readonly toReactive: UnwrapRef + readonly toRef: UnwrapRef + readonly toRefs: UnwrapRef + readonly toValue: UnwrapRef + readonly triggerRef: UnwrapRef + readonly trim: UnwrapRef + readonly tryOnBeforeMount: UnwrapRef + readonly tryOnBeforeUnmount: UnwrapRef + readonly tryOnMounted: UnwrapRef + readonly tryOnScopeDispose: UnwrapRef + readonly tryOnUnmounted: UnwrapRef + readonly unescape: UnwrapRef + readonly uniq: UnwrapRef + readonly uniqBy: UnwrapRef + readonly unref: UnwrapRef + readonly unrefElement: UnwrapRef + readonly until: UnwrapRef + readonly upperCase: UnwrapRef + readonly useActiveElement: UnwrapRef + readonly useAliyunCaptcha: UnwrapRef + readonly useAnimate: UnwrapRef + readonly useAppStore: UnwrapRef + readonly useArrayDifference: UnwrapRef + readonly useArrayEvery: UnwrapRef + readonly useArrayFilter: UnwrapRef + readonly useArrayFind: UnwrapRef + readonly useArrayFindIndex: UnwrapRef + readonly useArrayFindLast: UnwrapRef + readonly useArrayIncludes: UnwrapRef + readonly useArrayJoin: UnwrapRef + readonly useArrayMap: UnwrapRef + readonly useArrayReduce: UnwrapRef + readonly useArraySome: UnwrapRef + readonly useArrayUnique: UnwrapRef + readonly useAsyncQueue: UnwrapRef + readonly useAsyncState: UnwrapRef + readonly useAttrs: UnwrapRef + readonly useBase64: UnwrapRef + readonly useBattery: UnwrapRef + readonly useBluetooth: UnwrapRef + readonly useBreakpoints: UnwrapRef + readonly useBroadcastChannel: UnwrapRef + readonly useBrowserLocation: UnwrapRef + readonly useCached: UnwrapRef + readonly useCertification: UnwrapRef + readonly useClipboard: UnwrapRef + readonly useClipboardItems: UnwrapRef + readonly useCloned: UnwrapRef + readonly useColorMode: UnwrapRef + readonly useConfirmDialog: UnwrapRef + readonly useCountdown: UnwrapRef + readonly useCounter: UnwrapRef + readonly useCounterStore: UnwrapRef + readonly useCssModule: UnwrapRef + readonly useCssVar: UnwrapRef + readonly useCssVars: UnwrapRef + readonly useCurrentElement: UnwrapRef + readonly useCycleList: UnwrapRef + readonly useDark: UnwrapRef + readonly useDateFormat: UnwrapRef + readonly useDebounce: UnwrapRef + readonly useDebounceFn: UnwrapRef + readonly useDebouncedRefHistory: UnwrapRef + readonly useDeviceMotion: UnwrapRef + readonly useDeviceOrientation: UnwrapRef + readonly useDevicePixelRatio: UnwrapRef + readonly useDevicesList: UnwrapRef + readonly useDisplayMedia: UnwrapRef + readonly useDocumentVisibility: UnwrapRef + readonly useDraggable: UnwrapRef + readonly useDropZone: UnwrapRef + readonly useElementBounding: UnwrapRef + readonly useElementByPoint: UnwrapRef + readonly useElementHover: UnwrapRef + readonly useElementSize: UnwrapRef + readonly useElementVisibility: UnwrapRef + readonly useEventBus: UnwrapRef + readonly useEventListener: UnwrapRef + readonly useEventSource: UnwrapRef + readonly useEyeDropper: UnwrapRef + readonly useFavicon: UnwrapRef + readonly useFetch: UnwrapRef + readonly useFileDialog: UnwrapRef + readonly useFileSystemAccess: UnwrapRef + readonly useFocus: UnwrapRef + readonly useFocusWithin: UnwrapRef + readonly useFps: UnwrapRef + readonly useFullscreen: UnwrapRef + readonly useGamepad: UnwrapRef + readonly useGeolocation: UnwrapRef + readonly useId: UnwrapRef + readonly useIdle: UnwrapRef + readonly useImage: UnwrapRef + readonly useInfiniteScroll: UnwrapRef + readonly useIntersectionObserver: UnwrapRef + readonly useInterval: UnwrapRef + readonly useIntervalFn: UnwrapRef + readonly useKeyModifier: UnwrapRef + readonly useLastChanged: UnwrapRef + readonly useLink: UnwrapRef + readonly useLocalStorage: UnwrapRef + readonly useMagicKeys: UnwrapRef + readonly useManualRefHistory: UnwrapRef + readonly useMediaControls: UnwrapRef + readonly useMediaQuery: UnwrapRef + readonly useMemoize: UnwrapRef + readonly useMemory: UnwrapRef + readonly useMobileTable: UnwrapRef + readonly useModel: UnwrapRef + readonly useMounted: UnwrapRef + readonly useMouse: UnwrapRef + readonly useMouseInElement: UnwrapRef + readonly useMousePressed: UnwrapRef + readonly useMutationObserver: UnwrapRef + readonly useNavigatorLanguage: UnwrapRef + readonly useNetwork: UnwrapRef + readonly useNow: UnwrapRef + readonly useObjectUrl: UnwrapRef + readonly useOffsetPagination: UnwrapRef + readonly useOnline: UnwrapRef + readonly usePageLeave: UnwrapRef + readonly useParallax: UnwrapRef + readonly useParentElement: UnwrapRef + readonly usePerformanceObserver: UnwrapRef + readonly usePermission: UnwrapRef + readonly usePointer: UnwrapRef + readonly usePointerLock: UnwrapRef + readonly usePointerSwipe: UnwrapRef + readonly usePreferredColorScheme: UnwrapRef + readonly usePreferredContrast: UnwrapRef + readonly usePreferredDark: UnwrapRef + readonly usePreferredLanguages: UnwrapRef + readonly usePreferredReducedMotion: UnwrapRef + readonly usePreferredReducedTransparency: UnwrapRef + readonly usePrevious: UnwrapRef + readonly useRafFn: UnwrapRef + readonly useRefHistory: UnwrapRef + readonly useResizeObserver: UnwrapRef + readonly useRoute: UnwrapRef + readonly useRouter: UnwrapRef + readonly useSSRWidth: UnwrapRef + readonly useScreenOrientation: UnwrapRef + readonly useScreenSafeArea: UnwrapRef + readonly useScriptTag: UnwrapRef + readonly useScroll: UnwrapRef + readonly useScrollLock: UnwrapRef + readonly useSessionStorage: UnwrapRef + readonly useShare: UnwrapRef + readonly useSlots: UnwrapRef + readonly useSorted: UnwrapRef + readonly useSpeechRecognition: UnwrapRef + readonly useSpeechSynthesis: UnwrapRef + readonly useStepper: UnwrapRef + readonly useStorage: UnwrapRef + readonly useStorageAsync: UnwrapRef + readonly useStyleTag: UnwrapRef + readonly useSupported: UnwrapRef + readonly useSwipe: UnwrapRef + readonly useTemplateRef: UnwrapRef + readonly useTemplateRefsList: UnwrapRef + readonly useTextDirection: UnwrapRef + readonly useTextSelection: UnwrapRef + readonly useTextareaAutosize: UnwrapRef + readonly useThrottle: UnwrapRef + readonly useThrottleFn: UnwrapRef + readonly useThrottledRefHistory: UnwrapRef + readonly useTimeAgo: UnwrapRef + readonly useTimeAgoIntl: UnwrapRef + readonly useTimeout: UnwrapRef + readonly useTimeoutFn: UnwrapRef + readonly useTimeoutPoll: UnwrapRef + readonly useTimestamp: UnwrapRef + readonly useTitle: UnwrapRef + readonly useToNumber: UnwrapRef + readonly useToString: UnwrapRef + readonly useToggle: UnwrapRef + readonly useTransition: UnwrapRef + readonly useUrlSearchParams: UnwrapRef + readonly useUserMedia: UnwrapRef + readonly useUserStore: UnwrapRef + readonly useVModel: UnwrapRef + readonly useVModels: UnwrapRef + readonly useVibrate: UnwrapRef + readonly useVirtualList: UnwrapRef + readonly useWakeLock: UnwrapRef + readonly useWebNotification: UnwrapRef + readonly useWebSocket: UnwrapRef + readonly useWebWorker: UnwrapRef + readonly useWebWorkerFn: UnwrapRef + readonly useWindowFocus: UnwrapRef + readonly useWindowScroll: UnwrapRef + readonly useWindowSize: UnwrapRef + readonly validateEmail: UnwrapRef + readonly validateIdCard: UnwrapRef + readonly validatePhone: UnwrapRef + readonly version: UnwrapRef + readonly versionChecker: UnwrapRef + readonly watch: UnwrapRef + readonly watchArray: UnwrapRef + readonly watchAtMost: UnwrapRef + readonly watchDebounced: UnwrapRef + readonly watchDeep: UnwrapRef + readonly watchEffect: UnwrapRef + readonly watchIgnorable: UnwrapRef + readonly watchImmediate: UnwrapRef + readonly watchOnce: UnwrapRef + readonly watchPausable: UnwrapRef + readonly watchPostEffect: UnwrapRef + readonly watchSyncEffect: UnwrapRef + readonly watchThrottled: UnwrapRef + readonly watchTriggerable: UnwrapRef + readonly watchWithFilter: UnwrapRef + readonly whenever: UnwrapRef + } +} \ No newline at end of file diff --git a/components.d.ts b/components.d.ts new file mode 100644 index 0000000..a5016f5 --- /dev/null +++ b/components.d.ts @@ -0,0 +1,100 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +// biome-ignore lint: disable +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + AppBreadcrumb: typeof import('./src/components/layout/AppBreadcrumb.vue')['default'] + AppHeader: typeof import('./src/components/layout/AppHeader.vue')['default'] + AppLoading: typeof import('./src/components/common/AppLoading.vue')['default'] + AppSidebar: typeof import('./src/components/layout/AppSidebar.vue')['default'] + BusinessConsultationDialog: typeof import('./src/components/common/BusinessConsultationDialog.vue')['default'] + CertificationBanner: typeof import('./src/components/common/CertificationBanner.vue')['default'] + CertificationNotice: typeof import('./src/components/common/CertificationNotice.vue')['default'] + ChartCard: typeof import('./src/components/statistics/ChartCard.vue')['default'] + CodeDisplay: typeof import('./src/components/common/CodeDisplay.vue')['default'] + CustomSteps: typeof import('./src/components/common/CustomSteps.vue')['default'] + DanmakuBar: typeof import('./src/components/common/DanmakuBar.vue')['default'] + ElAlert: typeof import('element-plus/es')['ElAlert'] + ElAside: typeof import('element-plus/es')['ElAside'] + ElAvatar: typeof import('element-plus/es')['ElAvatar'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] + ElCard: typeof import('element-plus/es')['ElCard'] + ElCascader: typeof import('element-plus/es')['ElCascader'] + ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCol: typeof import('element-plus/es')['ElCol'] + ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] + 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'] + ElDrawer: typeof import('element-plus/es')['ElDrawer'] + ElDropdown: typeof import('element-plus/es')['ElDropdown'] + ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] + ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElEmpty: typeof import('element-plus/es')['ElEmpty'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + 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'] + ElLoading: typeof import('element-plus/es')['ElLoading'] + 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'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElRow: typeof import('element-plus/es')['ElRow'] + ElSegmented: typeof import('element-plus/es')['ElSegmented'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] + 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'] + ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTree: typeof import('element-plus/es')['ElTree'] + ElUpload: typeof import('element-plus/es')['ElUpload'] + ExportDialog: typeof import('./src/components/common/ExportDialog.vue')['default'] + FileUpload: typeof import('./src/components/common/FileUpload.vue')['default'] + FilterItem: typeof import('./src/components/common/FilterItem.vue')['default'] + FilterSection: typeof import('./src/components/common/FilterSection.vue')['default'] + FloatingCustomerService: typeof import('./src/components/common/FloatingCustomerService.vue')['default'] + HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] + ListPageLayout: typeof import('./src/components/common/ListPageLayout.vue')['default'] + MarkdownEditor: typeof import('./src/components/common/MarkdownEditor.vue')['default'] + NotificationPanel: typeof import('./src/components/layout/NotificationPanel.vue')['default'] + PermissionGuard: typeof import('./src/components/auth/PermissionGuard.vue')['default'] + ProductApiConfigDialog: typeof import('./src/components/admin/ProductApiConfigDialog.vue')['default'] + ProductCard: typeof import('./src/components/product/ProductCard.vue')['default'] + ProductDocumentationDialog: typeof import('./src/components/admin/ProductDocumentationDialog.vue')['default'] + ProductFormDialog: typeof import('./src/components/admin/ProductFormDialog.vue')['default'] + ResponsiveActionColumn: typeof import('./src/components/common/ResponsiveActionColumn.vue')['default'] + RichTextEditor: typeof import('./src/components/common/RichTextEditor.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + StatCard: typeof import('./src/components/statistics/StatCard.vue')['default'] + StatisticsDashboard: typeof import('./src/components/statistics/StatisticsDashboard.vue')['default'] + TheWelcome: typeof import('./src/components/TheWelcome.vue')['default'] + VersionInfo: typeof import('./src/components/common/VersionInfo.vue')['default'] + WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default'] + } + export interface GlobalDirectives { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } +} diff --git a/docs/API在线调试功能说明.md b/docs/API在线调试功能说明.md new file mode 100644 index 0000000..ab10d80 --- /dev/null +++ b/docs/API在线调试功能说明.md @@ -0,0 +1,202 @@ +# API在线调试功能说明 + +## 功能概述 + +API在线调试功能为用户提供了一个可视化的界面来测试已订阅的API接口,让用户能够: + +1. **选择产品**:从已订阅的产品列表中选择要调试的API +2. **输入参数**:根据产品要求输入测试参数 +3. **查看加密过程**:了解参数如何被加密处理 +4. **获取响应**:查看API的实际响应结果 +5. **分析结果**:了解API调用的完整过程 + +## 功能特点 + +### 1. 产品选择 +- 自动加载用户已订阅的产品列表 +- 显示产品名称、代码和价格信息 +- 只显示有效的订阅产品 + +### 2. 参数输入 +- 统一的参数结构:姓名、身份证号、手机号 +- 实时参数验证 +- 参数示例和说明 + +### 3. 加密处理 +- 自动使用用户的Secret Key进行AES加密 +- 显示原始参数和加密后的参数 +- 确保参数安全性 + +### 4. 调试结果 +- 完整的请求信息(产品名称、API代码、交易ID等) +- 请求时间和响应时间统计 +- 成功/失败状态显示 +- 原始响应数据展示 + +## 技术实现 + +### 后端接口 + +#### 1. 加密接口 +``` +POST /api/v1/encrypt +Content-Type: application/json +Authorization: Bearer {token} + +{ + "data": { + "name": "张三", + "id_card": "110101199001011234", + "mobile": "13800138000" + } +} +``` + +**响应**: +```json +{ + "success": true, + "data": { + "encrypted_data": "加密后的Base64字符串" + }, + "message": "加密成功" +} +``` + +#### 2. API调用接口 +``` +POST /api/v1/{api_code} +Content-Type: application/json +Access-Id: {access_id} + +{ + "data": "加密后的参数" +} +``` + +### 前端实现 + +#### 1. 页面结构 +- **调试配置区域**:产品选择、参数输入 +- **调试结果区域**:请求信息、响应数据 +- **使用说明区域**:操作指南和注意事项 + +#### 2. 核心功能 +- 自动加载用户订阅产品 +- 参数验证和格式化 +- 加密参数调用 +- 结果展示和分析 + +## 使用流程 + +### 1. 访问调试页面 +- 登录系统后,进入"开发者中心" → "在线调试" +- 页面会自动加载用户的API密钥和订阅产品 + +### 2. 选择产品 +- 从下拉列表中选择要调试的产品 +- 系统会显示产品的详细信息 + +### 3. 输入参数 +- 填写姓名(必填) +- 填写身份证号(必填,18位) +- 填写手机号(必填,11位) + +### 4. 开始调试 +- 点击"开始调试"按钮 +- 系统会自动加密参数并调用API +- 等待响应结果 + +### 5. 查看结果 +- 查看请求信息(交易ID、响应时间等) +- 查看原始参数和加密参数 +- 查看API响应结果 + +## 注意事项 + +### 1. 费用说明 +- 每次调试都会消耗API调用次数 +- 会按照产品价格扣除相应费用 +- 调试结果会记录在调用记录中 + +### 2. 数据安全 +- 所有参数都经过AES加密处理 +- 使用用户的专属Secret Key +- 不会在日志中记录敏感信息 + +### 3. 使用建议 +- 使用真实的测试数据进行调试 +- 注意参数格式的正确性 +- 调试结果仅供参考 + +### 4. 错误处理 +- 网络错误会显示相应提示 +- API调用失败会显示错误信息 +- 参数错误会给出具体提示 + +## 技术架构 + +### 前端架构 +``` +ApiDebugger.vue +├── 产品选择组件 +├── 参数输入组件 +├── 加密处理逻辑 +├── 结果展示组件 +└── 使用说明组件 +``` + +### 后端架构 +``` +API Handler +├── 加密接口 (EncryptParams) +├── API调用接口 (HandleApiCall) +└── 密钥管理接口 (GetUserApiKeys) +``` + +### 数据流 +1. 前端获取用户订阅产品 +2. 用户输入参数 +3. 前端调用加密接口 +4. 前端使用加密参数调用API +5. 后端处理并返回结果 +6. 前端展示调试结果 + +## 扩展功能 + +### 1. 参数模板 +- 支持保存常用的参数组合 +- 快速填充测试数据 +- 参数历史记录 + +### 2. 批量调试 +- 支持多个API同时调试 +- 批量参数输入 +- 结果对比分析 + +### 3. 调试历史 +- 保存调试记录 +- 结果回放功能 +- 性能统计分析 + +### 4. 高级功能 +- 自定义参数验证 +- 响应数据解析 +- 错误模式分析 + +## 常见问题 + +### Q1: 为什么看不到某些产品? +A: 只有已订阅且有效的产品才会显示在列表中。 + +### Q2: 调试失败怎么办? +A: 检查参数格式是否正确,网络连接是否正常,账户余额是否充足。 + +### Q3: 如何查看详细的错误信息? +A: 在调试结果区域会显示完整的错误信息和响应数据。 + +### Q4: 调试会消耗费用吗? +A: 是的,每次调试都会按照产品价格扣除相应费用。 + +### Q5: 如何保护我的API密钥? +A: Secret Key默认隐藏显示,可以点击"显示"按钮查看,建议不要泄露给他人。 \ No newline at end of file diff --git a/docs/API调用记录和钱包交易记录前端说明.md b/docs/API调用记录和钱包交易记录前端说明.md new file mode 100644 index 0000000..b765876 --- /dev/null +++ b/docs/API调用记录和钱包交易记录前端说明.md @@ -0,0 +1,512 @@ +# API调用记录和钱包交易记录前端功能说明 + +## 概述 + +本文档描述了新增的API调用记录和钱包交易记录前端页面功能,包括页面设计、交互逻辑和技术实现。 + +## 页面功能 + +### 1. API调用记录页面 (`/api/usage`) + +#### 页面布局 +- **标题**: "调用记录" +- **副标题**: "查看您的API调用历史记录" +- **布局组件**: `ListPageLayout` + +#### 功能模块 + +##### 1.1 统计信息区域 +- **总调用次数**: 显示用户的总API调用次数 +- **成功率**: 显示API调用的成功率百分比 +- **样式**: 卡片式设计,带有渐变背景和阴影效果 + +##### 1.2 筛选功能 +- **搜索调用**: 支持按交易ID或产品名称搜索 +- **调用状态**: 下拉选择(全部/成功/失败/处理中) +- **时间范围**: 日期时间范围选择器 +- **操作按钮**: 重置筛选、应用筛选 + +##### 1.3 数据表格 +**列定义**: +- **交易ID**: 显示API调用的唯一标识 +- **接口名称**: 显示调用的API接口名称和ID +- **状态**: 状态标签(成功/失败/处理中) +- **费用**: 显示调用产生的费用 +- **客户端IP**: 显示调用来源IP +- **调用时间**: 显示API调用的开始时间 +- **完成时间**: 显示API调用的完成时间 +- **操作**: 查看详情按钮 + +##### 1.4 详情弹窗 +**显示内容**: +- **基本信息**: 交易ID、状态、接口名称、费用、客户端IP +- **时间信息**: 调用时间、完成时间 +- **错误信息**: 错误类型和错误消息(如果有) +- **请求参数**: JSON格式的请求参数 +- **响应数据**: JSON格式的响应数据 + +##### 1.5 分页功能 +- **分页器**: 支持页码跳转、每页数量选择 +- **统计信息**: 显示总记录数和当前页信息 + +#### 交互逻辑 + +##### 搜索功能 +```javascript +// 防抖搜索,避免频繁请求 +const handleSearch = () => { + if (searchTimer) { + clearTimeout(searchTimer) + } + searchTimer = setTimeout(() => { + currentPage.value = 1 + loadApiCalls() + }, 500) +} +``` + +##### 筛选功能 +```javascript +// 处理筛选变化 +const handleFilterChange = () => { + currentPage.value = 1 + loadApiCalls() +} + +// 处理时间范围变化 +const handleDateRangeChange = (range) => { + if (range && range.length === 2) { + filters.start_time = range[0] + filters.end_time = range[1] + } else { + filters.start_time = '' + filters.end_time = '' + } + currentPage.value = 1 + loadApiCalls() +} +``` + +##### 数据加载 +```javascript +// 加载API调用记录 +const loadApiCalls = async () => { + loading.value = true + try { + const params = { + page: currentPage.value, + page_size: pageSize.value, + ...filters + } + + const response = await apiApi.getUserApiCalls(params) + apiCalls.value = response.data?.items || [] + total.value = response.data?.total || 0 + } catch (error) { + console.error('加载API调用记录失败:', error) + ElMessage.error('加载API调用记录失败') + } finally { + loading.value = false + } +} +``` + +### 2. 钱包交易记录页面 (`/finance/transactions`) + +#### 页面布局 +- **标题**: "消费记录" +- **副标题**: "查看您的钱包消费历史记录" +- **布局组件**: `ListPageLayout` + +#### 功能模块 + +##### 2.1 统计信息区域 +- **总消费次数**: 显示用户的总消费次数 +- **总消费金额**: 显示用户的总消费金额 +- **样式**: 卡片式设计,金额显示为红色突出 + +##### 2.2 筛选功能 +- **搜索交易**: 支持按API调用ID搜索 +- **金额范围**: 最小金额和最大金额输入框 +- **时间范围**: 日期时间范围选择器 +- **操作按钮**: 重置筛选、应用筛选 + +##### 2.3 数据表格 +**列定义**: +- **交易ID**: 显示钱包交易的唯一标识 +- **API调用ID**: 显示关联的API调用ID +- **消费金额**: 显示消费金额(红色突出) +- **消费时间**: 显示交易创建时间 +- **更新时间**: 显示交易最后更新时间 +- **操作**: 查看详情按钮 + +##### 2.4 详情弹窗 +**显示内容**: +- **基本信息**: 交易ID、用户ID、API调用ID、消费金额 +- **时间信息**: 创建时间、更新时间 +- **交易说明**: 解释交易记录的用途和含义 + +##### 2.5 分页功能 +- **分页器**: 支持页码跳转、每页数量选择 +- **统计信息**: 显示总记录数和当前页信息 + +#### 交互逻辑 + +##### 数据加载 +```javascript +// 加载钱包交易记录 +const loadTransactions = async () => { + loading.value = true + try { + const params = { + page: currentPage.value, + page_size: pageSize.value, + ...filters + } + + const response = await financeApi.getUserWalletTransactions(params) + transactions.value = response.data?.items || [] + total.value = response.data?.total || 0 + } catch (error) { + console.error('加载钱包交易记录失败:', error) + ElMessage.error('加载钱包交易记录失败') + } finally { + loading.value = false + } +} +``` + +##### 统计计算 +```javascript +// 加载统计数据 +const loadStats = async () => { + try { + // 计算统计数据 + const totalAmount = transactions.value.reduce((sum, transaction) => { + return sum + Number(transaction.amount || 0) + }, 0) + + stats.value = { + total_transactions: total.value, + total_amount: totalAmount + } + } catch (error) { + console.error('加载统计数据失败:', error) + } +} +``` + +## 技术实现 + +### 1. 组件架构 + +#### 通用组件 +- **ListPageLayout**: 列表页面布局组件 +- **FilterSection**: 筛选区域组件 +- **FilterItem**: 筛选项组件 +- **LoadingSpinner**: 加载动画组件 + +#### 页面组件 +- **Usage.vue**: API调用记录页面 +- **Transactions.vue**: 钱包交易记录页面 + +### 2. API接口 + +#### API调用记录接口 +```javascript +// API相关接口 +export const apiApi = { + // 用户API调用记录 + getUserApiCalls: (params) => request.get('/api/v1/my/api-calls', { params }) +} +``` + +#### 钱包交易记录接口 +```javascript +// 财务相关接口 +export const financeApi = { + // 钱包交易记录 + getUserWalletTransactions: (params) => request.get('/finance/wallet/transactions', { params }) +} +``` + +### 3. 状态管理 + +#### 响应式数据 +```javascript +// 页面数据 +const loading = ref(false) +const apiCalls = ref([]) +const total = ref(0) +const currentPage = ref(1) +const pageSize = ref(10) + +// 筛选条件 +const filters = reactive({ + keyword: '', + status: '', + start_time: '', + end_time: '' +}) + +// 统计数据 +const stats = ref({ + total_calls: 0, + success_rate: '0%' +}) +``` + +### 4. 工具函数 + +#### 格式化函数 +```javascript +// 格式化价格 +const formatPrice = (price) => { + if (!price) return '0.00' + return Number(price).toFixed(2) +} + +// 格式化日期 +const formatDate = (date) => { + if (!date) return '-' + return new Date(date).toLocaleDateString('zh-CN') +} + +// 格式化时间 +const formatTime = (date) => { + if (!date) return '-' + return new Date(date).toLocaleTimeString('zh-CN', { + hour: '2-digit', + minute: '2-digit' + }) +} + +// 格式化JSON +const formatJson = (jsonString) => { + try { + return JSON.stringify(JSON.parse(jsonString), null, 2) + } catch { + return jsonString + } +} +``` + +#### 状态处理函数 +```javascript +// 获取状态类型 +const getStatusType = (status) => { + switch (status) { + case 'success': + return 'success' + case 'failed': + return 'danger' + case 'pending': + return 'warning' + default: + return 'info' + } +} + +// 获取状态文本 +const getStatusText = (status) => { + switch (status) { + case 'success': + return '成功' + case 'failed': + return '失败' + case 'pending': + return '处理中' + default: + return '未知' + } +} +``` + +## 样式设计 + +### 1. 统计卡片样式 +```css +.stat-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 24px; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + border: 1px solid rgba(226, 232, 240, 0.6); + border-radius: 12px; + min-width: 120px; +} + +.stat-value { + font-size: 24px; + font-weight: 700; + color: #1e293b; + line-height: 1; + margin-bottom: 4px; +} + +.stat-label { + font-size: 13px; + color: #64748b; + font-weight: 500; +} +``` + +### 2. 详情弹窗样式 +```css +.detail-dialog :deep(.el-dialog) { + border-radius: 16px; + overflow: hidden; +} + +.detail-dialog :deep(.el-dialog__header) { + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border-bottom: 1px solid rgba(226, 232, 240, 0.6); + padding: 20px 24px; +} + +.detail-dialog :deep(.el-dialog__body) { + padding: 24px; +} +``` + +### 3. 表格样式优化 +```css +:deep(.el-table) { + border-radius: 8px; + overflow: hidden; +} + +:deep(.el-table th) { + background: #f8fafc !important; + border-bottom: 1px solid #e2e8f0; +} + +:deep(.el-table td) { + border-bottom: 1px solid #f1f5f9; +} + +:deep(.el-table tr:hover > td) { + background: #f8fafc !important; +} +``` + +### 4. 响应式设计 +```css +/* 响应式设计 */ +@media (max-width: 768px) { + .stat-item { + padding: 12px 16px; + min-width: 100px; + } + + .stat-value { + font-size: 20px; + } + + .stat-label { + font-size: 12px; + } + + .detail-item { + margin-bottom: 16px; + } + + .detail-label { + font-size: 13px; + } + + .detail-value { + font-size: 14px; + } +} +``` + +## 用户体验优化 + +### 1. 加载状态 +- **加载动画**: 使用Element Plus的LoadingSpinner组件 +- **骨架屏**: 数据加载时显示占位内容 +- **错误处理**: 友好的错误提示信息 + +### 2. 交互反馈 +- **防抖搜索**: 避免频繁的API请求 +- **即时反馈**: 操作后立即更新界面状态 +- **确认操作**: 重要操作需要用户确认 + +### 3. 数据展示 +- **分页加载**: 避免一次性加载大量数据 +- **虚拟滚动**: 大数据量时的性能优化 +- **数据缓存**: 减少重复请求 + +### 4. 移动端适配 +- **响应式布局**: 适配不同屏幕尺寸 +- **触摸友好**: 按钮和交互元素适合触摸操作 +- **性能优化**: 移动端性能优化 + +## 错误处理 + +### 1. 网络错误 +```javascript +try { + const response = await apiApi.getUserApiCalls(params) + // 处理成功响应 +} catch (error) { + console.error('加载API调用记录失败:', error) + ElMessage.error('加载API调用记录失败') +} +``` + +### 2. 数据验证 +```javascript +// 验证时间格式 +const handleDateRangeChange = (range) => { + if (range && range.length === 2) { + // 验证时间格式 + const startTime = new Date(range[0]) + const endTime = new Date(range[1]) + + if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) { + ElMessage.warning('时间格式不正确') + return + } + + filters.start_time = range[0] + filters.end_time = range[1] + } +} +``` + +### 3. 边界情况 +```javascript +// 处理空数据 +
+ + + 前往开发者中心 + + +
+``` + +## 性能优化 + +### 1. 组件优化 +- **懒加载**: 路由级别的组件懒加载 +- **缓存**: 使用keep-alive缓存页面状态 +- **虚拟化**: 大数据列表的虚拟滚动 + +### 2. 请求优化 +- **防抖**: 搜索输入防抖处理 +- **缓存**: API响应数据缓存 +- **预加载**: 关键数据的预加载 + +### 3. 渲染优化 +- **v-show vs v-if**: 合理使用条件渲染 +- **key属性**: 列表渲染时使用唯一key +- **计算属性**: 复杂计算的缓存 + +## 总结 + +API调用记录和钱包交易记录前端页面提供了完整的用户界面和交互体验,通过合理的组件设计、状态管理和样式优化,确保了良好的用户体验和系统性能。页面支持响应式设计,适配不同设备,并提供了丰富的筛选和查看功能。 \ No newline at end of file diff --git a/docs/产品API配置管理功能说明.md b/docs/产品API配置管理功能说明.md new file mode 100644 index 0000000..debe7c6 --- /dev/null +++ b/docs/产品API配置管理功能说明.md @@ -0,0 +1,164 @@ +# 产品API配置管理功能说明 + +## 功能概述 + +产品API配置管理功能允许管理员为每个产品配置API接口的请求参数、响应字段和响应示例,这些配置将用于前端的在线调试功能。 + +## 功能特性 + +### 1. 请求参数配置 +- **参数名称**: 显示给用户的参数名称(如:姓名) +- **字段名**: API接口中使用的字段名(如:name) +- **参数类型**: 支持文本、数字、密码、邮箱、手机号、身份证等类型 +- **是否必填**: 标识该参数是否为必填项 +- **参数示例**: 提供给用户的输入示例 +- **验证规则**: 正则表达式验证规则 +- **参数描述**: 参数的详细说明 + +### 2. 响应字段配置 +- **字段名称**: 显示给用户的字段名称(如:姓名) +- **字段路径**: JSON响应中的字段路径(如:data.name) +- **字段类型**: 支持字符串、数字、布尔值、对象、数组等类型 +- **是否必填**: 标识该字段是否在响应中必填 +- **字段示例**: 字段的示例值 +- **字段描述**: 字段的详细说明 + +### 3. 响应示例 +- **JSON格式**: 完整的API响应示例 +- **实时验证**: 确保输入的JSON格式正确 +- **格式化显示**: 自动格式化JSON内容 + +## 使用方法 + +### 1. 访问产品管理页面 +1. 登录管理员账户 +2. 进入"产品管理"页面 +3. 在产品列表中找到需要配置的产品 + +### 2. 配置API +1. 点击产品行的"配置API"按钮 +2. 在弹出的配置窗口中: + - 查看产品基本信息 + - 添加/编辑请求参数 + - 添加/编辑响应字段 + - 输入JSON响应示例 +3. 点击"创建"或"更新"保存配置 + +### 3. 配置示例 + +#### 请求参数配置示例 +```json +[ + { + "name": "姓名", + "field": "name", + "type": "text", + "required": true, + "description": "用户真实姓名", + "example": "张三", + "validation": "^[\\u4e00-\\u9fa5]{2,4}$" + }, + { + "name": "身份证号", + "field": "id_card", + "type": "idcard", + "required": true, + "description": "用户身份证号码", + "example": "110101199001011234", + "validation": "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$" + } +] +``` + +#### 响应字段配置示例 +```json +[ + { + "name": "结果", + "path": "result", + "type": "string", + "required": true, + "description": "查询结果", + "example": "success" + }, + { + "name": "数据", + "path": "data", + "type": "object", + "required": false, + "description": "查询结果数据", + "example": "{}" + } +] +``` + +#### 响应示例 +```json +{ + "result": "success", + "code": 200, + "message": "查询成功", + "data": { + "name": "张三", + "id_card": "110101199001011234", + "mobile": "13800138000", + "status": "正常", + "query_time": "2026-01-01 12:00:00" + } +} +``` + +## 技术实现 + +### 后端架构 +- **实体层**: `ProductApiConfig` 实体 +- **仓库层**: `ProductApiConfigRepository` 接口和实现 +- **领域服务层**: `ProductApiConfigService` 业务逻辑 +- **应用服务层**: `ProductApiConfigApplicationService` 业务流程 +- **HTTP层**: `ProductAdminHandler` 接口处理 + +### 前端架构 +- **组件**: `ProductApiConfigDialog.vue` 配置弹窗 +- **页面**: 集成到产品管理页面 +- **API**: 通过 `productAdminApi` 调用后端接口 + +### 数据库设计 +```sql +CREATE TABLE product_api_configs ( + id VARCHAR(36) PRIMARY KEY, + product_id VARCHAR(36) NOT NULL UNIQUE, + request_params JSON NOT NULL, + response_fields JSON NOT NULL, + response_example JSON NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted_at TIMESTAMP NULL, + FOREIGN KEY (product_id) REFERENCES products(id) +); +``` + +## 初始化脚本 + +项目提供了初始化脚本 `scripts/init_product_api_configs.go`,可以为现有产品创建默认的API配置: + +```bash +cd hyapi-server +go run scripts/init_product_api_configs.go +``` + +## 注意事项 + +1. **唯一性**: 每个产品只能有一个API配置 +2. **JSON格式**: 响应示例必须是有效的JSON格式 +3. **字段路径**: 响应字段路径使用点号分隔(如:data.name) +4. **验证规则**: 支持正则表达式验证规则 +5. **权限控制**: 只有管理员可以配置API + +## 扩展功能 + +未来可以考虑添加的功能: +1. **配置模板**: 提供常用的配置模板 +2. **批量导入**: 支持批量导入配置 +3. **版本管理**: 支持配置版本管理 +4. **测试功能**: 集成API测试功能 +5. **文档生成**: 自动生成API文档 diff --git a/docs/充值功能前端说明.md b/docs/充值功能前端说明.md new file mode 100644 index 0000000..a868e43 --- /dev/null +++ b/docs/充值功能前端说明.md @@ -0,0 +1,196 @@ +# 充值功能前端说明 + +## 概述 + +前端充值功能包含两个主要页面: +1. **钱包充值页面** (`/finance/wallet`) - 用户选择充值方式并进行充值 +2. **充值成功页面** (`/finance/wallet/success`) - 支付宝充值成功后的重定向页面 + +## 页面功能 + +### 1. 钱包充值页面 (`Wallet.vue`) + +#### 功能特性 +- **钱包余额展示** - 显示用户当前钱包余额 +- **充值方式选择** - 支持支付宝充值和对公转账两种方式 +- **支付宝充值** - 输入充值金额,跳转到支付宝支付(待接入) +- **对公转账** - 显示银行账户信息,支持转账记录提交 + +#### 页面结构 +``` +钱包充值页面 +├── 页面头部 +│ ├── 标题:钱包充值 +│ └── 返回按钮 +├── 钱包余额信息 +│ ├── 余额图标 +│ └── 当前余额显示 +├── 充值方式选择 +│ ├── 支付宝充值卡片 +│ └── 对公转账卡片 +├── 支付宝充值表单(选中时显示) +│ ├── 充值金额输入 +│ └── 立即充值按钮 +└── 对公转账信息(选中时显示) + ├── 银行账户信息 + ├── 转账说明 + └── 转账记录表单 +``` + +#### 交互流程 + +**支付宝充值流程:** +1. 用户选择"支付宝充值" +2. 输入充值金额(最低1元) +3. 点击"立即充值" +4. 确认充值金额 +5. 跳转到支付宝支付页面(待实现) +6. 支付成功后跳转到充值成功页面 + +**对公转账流程:** +1. 用户选择"对公转账" +2. 查看银行账户信息 +3. 复制银行账号(可选) +4. 进行银行转账 +5. 填写转账记录表单 +6. 提交转账记录 +7. 等待人工确认(1-2个工作日) + +### 2. 充值成功页面 (`WalletSuccess.vue`) + +#### 功能特性 +- **成功状态展示** - 显示充值成功图标和标题 +- **充值详情** - 显示充值金额、方式、时间、当前余额 +- **操作按钮** - 提供多个后续操作选项 +- **温馨提示** - 显示相关提示信息 + +#### 页面结构 +``` +充值成功页面 +├── 成功状态 +│ ├── 成功图标 +│ ├── 成功标题 +│ └── 成功副标题 +├── 充值详情 +│ ├── 充值金额 +│ ├── 充值方式 +│ ├── 充值时间 +│ └── 当前余额 +├── 操作按钮 +│ ├── 返回财务中心 +│ ├── 去订阅产品 +│ └── 开发者中心 +└── 温馨提示 + └── 相关提示信息 +``` + +## API接口 + +### 财务相关接口 + +```javascript +// 获取钱包信息 +financeApi.getWallet() + +// 对公转账充值 +financeApi.transferRecharge({ + amount: 100.00, + transfer_order_id: 'TR202612010001', + bank_account: '6222021234567890123', + bank_name: '中国工商银行', + notes: '转账备注' +}) + +// 赠送充值(管理员使用) +financeApi.giftRecharge({ + amount: 50.00, + gift_reason: '新用户注册奖励', + notes: '系统自动赠送' +}) +``` + +## 样式设计 + +### 设计风格 +- 遵循项目整体设计风格 +- 使用渐变色彩和卡片式布局 +- 响应式设计,支持移动端 +- 统一的颜色和字体规范 + +### 主要样式特点 +- **渐变背景** - 钱包余额卡片使用紫色渐变 +- **卡片布局** - 充值方式选择使用卡片式设计 +- **悬停效果** - 充值方式卡片支持悬停动画 +- **响应式** - 移动端适配,布局自适应 + +## 配置信息 + +### 对公转账信息配置 +```javascript +const transferInfo = { + bankName: '中国工商银行', + bankAccount: '6222021234567890123', + accountName: '某某科技有限公司' +} +``` + +### 用户信息配置 +```javascript +const userInfo = { + userId: 'USER123456' // 实际应从用户状态获取 +} +``` + +## 路由配置 + +```javascript +{ + path: '/finance', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: 'wallet', + name: 'Wallet', + component: () => import('@/pages/finance/Wallet.vue'), + meta: { title: '余额充值' } + }, + { + path: 'wallet/success', + name: 'WalletSuccess', + component: () => import('@/pages/finance/WalletSuccess.vue'), + meta: { title: '充值成功' } + } + ] +} +``` + +## 后续开发计划 + +### 1. 支付宝支付集成 +- 集成支付宝支付SDK +- 实现支付页面跳转 +- 处理支付回调 +- 添加支付状态查询 + +### 2. 充值记录页面 +- 创建充值记录列表页面 +- 支持按类型、状态筛选 +- 显示充值详情和状态 + +### 3. 实时余额更新 +- 实现WebSocket实时余额更新 +- 添加余额变动通知 + +### 4. 移动端优化 +- 优化移动端交互体验 +- 添加手势操作支持 + +## 注意事项 + +1. **金额验证** - 充值金额必须大于等于1元 +2. **表单验证** - 所有必填字段都有相应的验证规则 +3. **错误处理** - 完善的错误提示和异常处理 +4. **用户体验** - 加载状态、成功提示等交互反馈 +5. **安全性** - 敏感信息(如银行账号)的显示和复制功能 +6. **响应式** - 确保在不同设备上的良好显示效果 \ No newline at end of file diff --git a/docs/支付宝充值功能说明.md b/docs/支付宝充值功能说明.md new file mode 100644 index 0000000..492dbe6 --- /dev/null +++ b/docs/支付宝充值功能说明.md @@ -0,0 +1,295 @@ +# 支付宝充值功能说明 + +## 功能概述 + +本功能实现了完整的支付宝充值流程,从前端用户操作到后端支付处理,再到支付回调确认,形成了完整的闭环。 + +## 功能特性 + +### 1. 前端功能 +- **钱包余额显示**:实时显示用户当前钱包余额和状态 +- **充值方式选择**:支持支付宝充值和对公转账两种方式 +- **支付宝充值表单**:用户输入充值金额,系统创建支付订单 +- **支付跳转**:自动跳转到支付宝支付页面 +- **支付结果页面**: + - 支付成功页面:显示充值详情和操作按钮 + - 支付失败页面:显示失败原因和重试选项 + +### 2. 后端功能 +- **支付宝订单创建**:生成唯一订单号,创建支付宝支付订单 +- **异步回调处理**:处理支付宝支付成功通知,更新钱包余额 +- **同步回调处理**:处理用户支付完成后的页面跳转 +- **充值记录管理**:完整的充值记录创建、查询和管理 +- **事务保护**:确保充值过程的原子性和数据一致性 + +## 技术架构 + +### 前端架构 +``` +Wallet.vue (钱包页面) +├── 余额显示 +├── 充值方式选择 +├── 支付宝充值表单 +└── 支付跳转逻辑 + +WalletSuccess.vue (支付成功页面) +├── 成功状态显示 +├── 充值详情展示 +└── 操作按钮 + +WalletFail.vue (支付失败页面) +├── 失败状态显示 +├── 失败原因说明 +└── 重试和客服选项 +``` + +### 后端架构 +``` +FinanceHandler (HTTP处理器) +├── CreateAlipayRecharge (创建充值订单) +├── HandleAlipayCallback (异步回调处理) +└── HandleAlipayReturn (同步回调处理) + +FinanceApplicationService (应用服务) +├── CreateAlipayRechargeOrder (业务流程编排) +├── HandleAlipayCallback (回调处理) +└── GetUserRechargeRecords (充值记录查询) + +RechargeRecordService (充值记录服务) +├── CreateAlipayRecharge (创建充值记录) +├── CreateAlipayOrder (创建支付宝订单) +└── HandleAlipayPaymentSuccess (支付成功处理) + +AliPayService (支付宝服务) +├── CreateAlipayOrder (创建支付订单) +├── HandleAliPaymentNotification (回调验证) +└── GenerateOutTradeNo (生成订单号) +``` + +## API接口 + +### 1. 创建支付宝充值订单 +``` +POST /api/v1/finance/wallet/alipay-recharge +Content-Type: application/json +Authorization: Bearer + +{ + "amount": 100.00, + "subject": "钱包充值 ¥100.00", + "platform": "pc" +} + +Response: +{ + "code": 200, + "message": "支付宝充值订单创建成功", + "data": { + "pay_url": "https://openapi.alipay.com/...", + "out_trade_no": "202612011234567890", + "amount": 100.00, + "platform": "pc", + "subject": "钱包充值 ¥100.00" + } +} +``` + +### 2. 支付宝异步回调 +``` +POST /api/v1/finance/alipay/callback +Content-Type: application/x-www-form-urlencoded + +支付宝回调参数... + +Response: "success" +``` + +### 3. 支付宝同步回调 +``` +GET /api/v1/finance/alipay/return?out_trade_no=xxx&trade_no=xxx&trade_status=TRADE_SUCCESS&total_amount=100.00 + +Response: 302 Redirect to frontend success/fail page +``` + +### 4. 获取用户充值记录 +``` +GET /api/v1/finance/wallet/recharge-records?page=1&page_size=10&recharge_type=alipay&status=success +Authorization: Bearer + +Response: +{ + "code": 200, + "message": "获取充值记录成功", + "data": { + "items": [...], + "total": 50, + "page": 1, + "size": 10 + } +} +``` + +## 支付流程 + +### 1. 用户充值流程 +1. 用户进入钱包页面,查看当前余额 +2. 选择"支付宝充值"方式 +3. 输入充值金额(最低1元) +4. 点击"立即充值"按钮 +5. 前端调用后端创建充值订单接口 +6. 后端生成订单号,创建支付宝支付订单 +7. 返回支付链接给前端 +8. 前端自动跳转到支付宝支付页面 +9. 用户在支付宝完成支付 +10. 支付宝跳转回系统同步回调地址 +11. 后端处理同步回调,跳转到前端成功/失败页面 +12. 支付宝异步通知后端支付结果 +13. 后端处理异步回调,更新钱包余额和充值记录状态 + +### 2. 支付回调处理 +- **异步回调**:支付宝主动通知支付结果,用于更新订单状态和钱包余额 +- **同步回调**:用户支付完成后页面跳转,用于用户体验优化 + +## 数据库设计 + +### 1. 充值记录表 (recharge_records) +```sql +CREATE TABLE recharge_records ( + id VARCHAR(36) PRIMARY KEY, + user_id VARCHAR(36) NOT NULL, + amount DECIMAL(20,8) NOT NULL, + recharge_type VARCHAR(20) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + alipay_order_id VARCHAR(64) UNIQUE, + transfer_order_id VARCHAR(64) UNIQUE, + notes VARCHAR(500), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted_at TIMESTAMP NULL +); +``` + +### 2. 支付宝订单表 (alipay_orders) +```sql +CREATE TABLE alipay_orders ( + id VARCHAR(36) PRIMARY KEY, + recharge_id VARCHAR(36) NOT NULL UNIQUE, + out_trade_no VARCHAR(64) NOT NULL UNIQUE, + trade_no VARCHAR(64) UNIQUE, + subject VARCHAR(200) NOT NULL, + amount DECIMAL(20,8) NOT NULL, + platform VARCHAR(20) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + buyer_id VARCHAR(64), + seller_id VARCHAR(64), + pay_amount DECIMAL(20,8), + receipt_amount DECIMAL(20,8), + notify_time TIMESTAMP NULL, + return_time TIMESTAMP NULL, + error_code VARCHAR(64), + error_message TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); +``` + +## 配置说明 + +### 1. 支付宝配置 +```yaml +alipay: + app_id: "2021004181633376" + private_key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSj..." + alipay_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..." + is_production: true + notify_url: "https://console.haiyudata.com/api/v1/finance/alipay/callback" + return_url: "https://console.haiyudata.com/api/v1/finance/alipay/return" +``` + +### 2. 环境配置 +- **开发环境**:使用沙箱环境,回调地址指向开发服务器 +- **生产环境**:使用正式环境,回调地址指向生产服务器 + +## 安全考虑 + +### 1. 支付安全 +- 使用支付宝官方SDK进行签名验证 +- 异步回调验证签名确保数据真实性 +- 订单号唯一性检查防止重复处理 +- 金额验证确保支付金额正确 + +### 2. 数据安全 +- 所有敏感操作使用事务保护 +- 充值记录状态变更的原子性 +- 钱包余额更新的并发控制 +- 完整的操作日志记录 + +## 错误处理 + +### 1. 支付失败处理 +- 网络异常:提示用户重试 +- 余额不足:提示用户检查支付账户 +- 订单超时:自动取消订单,允许重新创建 +- 系统异常:记录错误日志,提供客服支持 + +### 2. 回调异常处理 +- 签名验证失败:记录异常,不处理回调 +- 重复回调:幂等性处理,避免重复更新 +- 数据不一致:记录异常,人工介入处理 + +## 监控和日志 + +### 1. 关键日志点 +- 充值订单创建 +- 支付宝回调接收 +- 支付成功处理 +- 钱包余额更新 +- 异常错误记录 + +### 2. 监控指标 +- 充值成功率 +- 支付响应时间 +- 回调处理延迟 +- 异常错误率 + +## 测试建议 + +### 1. 功能测试 +- 正常充值流程测试 +- 支付失败场景测试 +- 网络异常处理测试 +- 并发充值测试 + +### 2. 安全测试 +- 回调签名验证测试 +- 重复订单处理测试 +- 金额篡改防护测试 +- 并发安全测试 + +## 部署注意事项 + +### 1. 环境配置 +- 确保支付宝配置正确 +- 回调地址可访问性 +- 数据库连接稳定性 +- 日志存储空间充足 + +### 2. 监控告警 +- 支付成功率监控 +- 系统异常告警 +- 数据库性能监控 +- 网络连接监控 + +## 后续优化 + +### 1. 功能增强 +- 支持更多支付方式(微信支付、银行卡等) +- 充值优惠活动 +- 自动充值功能 +- 充值提醒功能 + +### 2. 性能优化 +- 支付订单缓存 +- 异步处理优化 +- 数据库查询优化 +- 前端体验优化 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..5e7e5e1 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,121 @@ +import js from '@eslint/js' +import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' +import pluginOxlint from 'eslint-plugin-oxlint' +import pluginVue from 'eslint-plugin-vue' +import { defineConfig, globalIgnores } from 'eslint/config' +import globals from 'globals' + +export default defineConfig([ + { + name: 'app/files-to-lint', + files: ['**/*.{js,mjs,jsx,vue}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + { + languageOptions: { + globals: { + ...globals.browser, + // 自动引入的全局变量 + 'ref': 'readonly', + 'reactive': 'readonly', + 'computed': 'readonly', + 'watch': 'readonly', + 'watchEffect': 'readonly', + 'onMounted': 'readonly', + 'onUnmounted': 'readonly', + 'onBeforeMount': 'readonly', + 'onBeforeUnmount': 'readonly', + 'nextTick': 'readonly', + 'defineComponent': 'readonly', + 'h': 'readonly', + 'inject': 'readonly', + 'provide': 'readonly', + 'toRef': 'readonly', + 'toRefs': 'readonly', + 'unref': 'readonly', + 'isRef': 'readonly', + 'isReactive': 'readonly', + 'markRaw': 'readonly', + 'shallowRef': 'readonly', + 'shallowReactive': 'readonly', + 'readonly': 'readonly', + 'shallowReadonly': 'readonly', + 'toRaw': 'readonly', + 'customRef': 'readonly', + 'triggerRef': 'readonly', + 'getCurrentInstance': 'readonly', + 'useAttrs': 'readonly', + 'useSlots': 'readonly', + 'useCssModule': 'readonly', + 'useCssVars': 'readonly', + 'useId': 'readonly', + 'useModel': 'readonly', + 'useTemplateRef': 'readonly', + 'useLink': 'readonly', + 'useRoute': 'readonly', + 'useRouter': 'readonly', + 'onBeforeRouteLeave': 'readonly', + 'onBeforeRouteUpdate': 'readonly', + 'defineStore': 'readonly', + 'storeToRefs': 'readonly', + 'createPinia': 'readonly', + 'setActivePinia': 'readonly', + 'getActivePinia': 'readonly', + 'mapState': 'readonly', + 'mapGetters': 'readonly', + 'mapActions': 'readonly', + 'mapStores': 'readonly', + 'mapWritableState': 'readonly', + 'ElMessage': 'readonly', + 'ElMessageBox': 'readonly', + 'ElNotification': 'readonly', + 'ElLoading': 'readonly', + 'axios': 'readonly', + 'dayjs': 'readonly', + 'isEmpty': 'readonly', + 'isEqual': 'readonly', + 'get': 'readonly', + 'set': 'readonly', + 'omit': 'readonly', + 'pick': 'readonly', + 'merge': 'readonly', + 'uniq': 'readonly', + 'uniqBy': 'readonly', + 'groupBy': 'readonly', + 'keyBy': 'readonly', + 'sortBy': 'readonly', + 'orderBy': 'readonly', + 'filter': 'readonly', + 'map': 'readonly', + 'reduce': 'readonly', + 'find': 'readonly', + 'findIndex': 'readonly', + 'includes': 'readonly', + 'startsWith': 'readonly', + 'endsWith': 'readonly', + 'camelCase': 'readonly', + 'kebabCase': 'readonly', + 'snakeCase': 'readonly', + 'capitalize': 'readonly', + 'upperCase': 'readonly', + 'lowerCase': 'readonly', + 'trim': 'readonly', + 'escape': 'readonly', + 'unescape': 'readonly', + }, + }, + }, + + js.configs.recommended, + ...pluginVue.configs['flat/essential'], + ...pluginOxlint.configs['flat/recommended'], + { + rules: { + 'vue/multi-word-component-names': 'off', + 'no-unused-vars': 'warn', + }, + }, + skipFormatting, +]) diff --git a/index.html b/index.html new file mode 100644 index 0000000..dda915f --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + 海宇数据 + + + + + +
+
+ + + diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..5a1f2d2 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..03d1d88 --- /dev/null +++ b/package.json @@ -0,0 +1,62 @@ +{ + "name": "hyapi-web", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore", + "lint:eslint": "eslint . --fix", + "lint": "run-s lint:*", + "format": "prettier --write src/" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@heroicons/vue": "^2.2.0", + "@tailwindcss/vite": "^4.1.11", + "@tinymce/tinymce-vue": "^6.3.0", + "@vueuse/core": "^13.5.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12", + "axios": "^1.11.0", + "color4bg": "^0.1.1", + "dayjs": "^1.11.13", + "echarts": "^6.0.0", + "element-plus": "^2.10.4", + "lodash-es": "^4.17.21", + "marked": "^16.1.1", + "pinia": "^3.0.3", + "qrcode": "^1.5.4", + "tinymce": "^8.0.2", + "unplugin-auto-import": "^19.3.0", + "unplugin-vue-components": "^28.8.0", + "vue": "^3.5.18", + "vue-json-editor": "^1.4.3", + "vue-pdf-embed": "^2.1.3", + "vue-router": "^4.5.1", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.32.0", + "@tailwindcss/forms": "^0.5.10", + "@tailwindcss/typography": "^0.5.16", + "@vitejs/plugin-vue": "^6.0.1", + "@vue/eslint-config-prettier": "^10.2.0", + "autoprefixer": "^10.4.21", + "eslint": "^9.32.0", + "eslint-plugin-oxlint": "~1.8.0", + "eslint-plugin-vue": "~10.3.0", + "globals": "^16.3.0", + "npm-run-all2": "^8.0.4", + "oxlint": "~1.8.0", + "postcss": "^8.5.6", + "prettier": "3.6.2", + "sass-embedded": "^1.89.2", + "tailwindcss": "^4.1.11", + "vite": "^7.0.6", + "vite-plugin-vue-devtools": "^8.0.0" + }, + "packageManager": "pnpm@10.13.1" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..ab6d5ef --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5255 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.1 + version: 2.3.2(vue@3.5.32) + '@heroicons/vue': + specifier: ^2.2.0 + version: 2.2.0(vue@3.5.32) + '@tailwindcss/vite': + specifier: ^4.1.11 + version: 4.2.4(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)) + '@tinymce/tinymce-vue': + specifier: ^6.3.0 + version: 6.3.0(tinymce@8.4.0)(vue@3.5.32) + '@vueuse/core': + specifier: ^13.5.0 + version: 13.9.0(vue@3.5.32) + '@wangeditor/editor': + specifier: ^5.1.23 + version: 5.1.23 + '@wangeditor/editor-for-vue': + specifier: ^5.1.12 + version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.32) + axios: + specifier: ^1.11.0 + version: 1.15.2 + color4bg: + specifier: ^0.1.1 + version: 0.1.1 + dayjs: + specifier: ^1.11.13 + version: 1.11.20 + echarts: + specifier: ^6.0.0 + version: 6.0.0 + element-plus: + specifier: ^2.10.4 + version: 2.13.7(vue@3.5.32) + lodash-es: + specifier: ^4.17.21 + version: 4.18.1 + marked: + specifier: ^16.1.1 + version: 16.4.2 + pinia: + specifier: ^3.0.3 + version: 3.0.4(vue@3.5.32) + qrcode: + specifier: ^1.5.4 + version: 1.5.4 + tinymce: + specifier: ^8.0.2 + version: 8.4.0 + unplugin-auto-import: + specifier: ^19.3.0 + version: 19.3.0(@vueuse/core@13.9.0(vue@3.5.32)) + unplugin-vue-components: + specifier: ^28.8.0 + version: 28.8.0(@babel/parser@7.29.2)(vue@3.5.32) + vue: + specifier: ^3.5.18 + version: 3.5.32 + vue-json-editor: + specifier: ^1.4.3 + version: 1.4.3 + vue-pdf-embed: + specifier: ^2.1.3 + version: 2.1.4(vue@3.5.32) + vue-router: + specifier: ^4.5.1 + version: 4.6.4(vue@3.5.32) + vuedraggable: + specifier: ^4.1.0 + version: 4.1.0(vue@3.5.32) + devDependencies: + '@eslint/js': + specifier: ^9.32.0 + version: 9.39.4 + '@tailwindcss/forms': + specifier: ^0.5.10 + version: 0.5.11(tailwindcss@4.2.4) + '@tailwindcss/typography': + specifier: ^0.5.16 + version: 0.5.19(tailwindcss@4.2.4) + '@vitejs/plugin-vue': + specifier: ^6.0.1 + version: 6.0.6(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0))(vue@3.5.32) + '@vue/eslint-config-prettier': + specifier: ^10.2.0 + version: 10.2.0(eslint@9.39.4(jiti@2.6.1))(prettier@3.6.2) + autoprefixer: + specifier: ^10.4.21 + version: 10.5.0(postcss@8.5.10) + eslint: + specifier: ^9.32.0 + version: 9.39.4(jiti@2.6.1) + eslint-plugin-oxlint: + specifier: ~1.8.0 + version: 1.8.0 + eslint-plugin-vue: + specifier: ~10.3.0 + version: 10.3.0(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) + globals: + specifier: ^16.3.0 + version: 16.5.0 + npm-run-all2: + specifier: ^8.0.4 + version: 8.0.4 + oxlint: + specifier: ~1.8.0 + version: 1.8.0 + postcss: + specifier: ^8.5.6 + version: 8.5.10 + prettier: + specifier: 3.6.2 + version: 3.6.2 + sass-embedded: + specifier: ^1.89.2 + version: 1.99.0 + tailwindcss: + specifier: ^4.1.11 + version: 4.2.4 + vite: + specifier: ^7.0.6 + version: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + vite-plugin-vue-devtools: + specifier: ^8.0.0 + version: 8.1.1(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0))(vue@3.5.32) + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-decorators@7.29.0': + resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.28.6': + resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bufbuild/protobuf@2.11.0': + resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@heroicons/vue@2.2.0': + resolution: {integrity: sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==} + peerDependencies: + vue: '>= 3' + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/canvas-android-arm64@0.1.99': + resolution: {integrity: sha512-9OCRt8VVxA17m32NWZKyNC2qamdaS/SC5CEOIQwFngRq0DIeVm4PDal+6Ljnhqm2whZiC63DNuKZ4xSp2nbj9w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@0.1.99': + resolution: {integrity: sha512-lupMDMy1+H38dhyCcLirOKKVUyzzlxi7j7rGPLI3vViMHOoPjcXO1b10ivy+ad+q6MiwHfoLjKTCoLke5ySOBg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@0.1.99': + resolution: {integrity: sha512-fdz02t4w8n6Ii/rYhWig6STb/zcTmCC/6YZTGmjoDeidDwn9Wf0ukQVynhCPEs29vqUc66wHZKsuIgMs9tycCg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.99': + resolution: {integrity: sha512-w4FwVwlNo00ezeRhfY62IVIyt6G3u8wodkPtiqWc52BUHx+VDBUM2vkS3ogfANaLI7hnf3s6WK4LyZVUjBg1lA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@0.1.99': + resolution: {integrity: sha512-8JvHeexKQ8c7g0q7YJ29NVQwnf1ePghP9ys9ZN0R0qzyqJQ9Uw6N9qnDINArlm3IYHexB7LjzArIfhQiqSDGvQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-arm64-musl@0.1.99': + resolution: {integrity: sha512-Z+6nyLdJXWzLPVxi4H6g9TJop4DwN3KSgHWto5JCbZV5/uKoVqcSynPs0tGlUHOoWI8S8tEvJspz51GQkvr07w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.99': + resolution: {integrity: sha512-jAnfOUv4IO1l8Levk5t85oVtEBOXLa07KnIUgWo1CDlPxiqpxS3uBfiE38Lvj/CQgHaNF6Nxk/SaemwLgsVJgw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-gnu@0.1.99': + resolution: {integrity: sha512-mIkXw3fGmbYyFjSmfWEvty4jN+rwEOmv0+Dy9bRvvTzLYWCgm3RMgUEQVfAKFw96nIRFnyNZiK83KNQaVVFjng==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-musl@0.1.99': + resolution: {integrity: sha512-f3Uz2P0RgrtBHISxZqr6yiYXJlTDyCVBumDacxo+4AmSg7z0HiqYZKGWC/gszq3fbPhyQUya1W2AEteKxT9Y6A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-win32-arm64-msvc@0.1.99': + resolution: {integrity: sha512-XE6KUkfqRsCNejcoRMiMr3RaUeObxNf6y7dut3hrq2rn7PzfRTZgrjF1F/B2C7FcdgqY/vSHWpQeMuNz1vTNHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/canvas-win32-x64-msvc@0.1.99': + resolution: {integrity: sha512-plMYGVbc/vmmPF9MtmHbwNk1rL1Aj53vQZt+Gnv1oZn6gmd9jEHHJ0n9Nd2nxa5sKH7TS5IjkCDM6289O0d6PQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@0.1.99': + resolution: {integrity: sha512-zN4eQlK3eBf7aJBcTHZilpBH3tDekBzPMIWC8r0s94Ecl73XfOyFi4w7yKFMRVUT0lvNQjtOL8YSrwqQj6mZFg==} + engines: {node: '>= 10'} + + '@oxlint/darwin-arm64@1.8.0': + resolution: {integrity: sha512-1juYJF1xqRNkswzDSN1V44NoZ+O2Mkc9LjbkDB/UErb8dxTqFhCZC3CQR6Em55/tys1FtajXgK3B+ykWnY9HNQ==} + cpu: [arm64] + os: [darwin] + + '@oxlint/darwin-x64@1.8.0': + resolution: {integrity: sha512-5b7J/XE2eGhx3+vw6IFuuL0BbIF3wRzo4SWHVXN9rO3WYq2YpoHToY4C5WMWb8toVZcoJlx4Y1lq3IO2V78zTg==} + cpu: [x64] + os: [darwin] + + '@oxlint/linux-arm64-gnu@1.8.0': + resolution: {integrity: sha512-pzfk9IZBbYuIYn4sbT//Vox8B8e8hOZPkIQnNAdzhpGtRjV4NYOgNL5/h2QZC+ecmxl8H+Gi9WV6dyKjFrBtcw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/linux-arm64-musl@1.8.0': + resolution: {integrity: sha512-6rpaeAG271wbUNM+WeJhdvJDDMwfoenm7rPY304dxnC+fcuR8Q0LSv09dGeNWrsqjjZuDP9R10qR154nysBxFg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/linux-x64-gnu@1.8.0': + resolution: {integrity: sha512-qPEF8tKMu+63b58gPfwU3KyJf2z9KyorbrC0yGXFHQLzRPEtrh6bAjf+AzCs3n8WhDR1K6jPgcPT4Sp8bahCyQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/linux-x64-musl@1.8.0': + resolution: {integrity: sha512-JyErk/LsLg/tA3XkHhU8VIxahOdq56L99mbpMFGLTkOQgtnhY2MDAYULVgOuFFX3v6Q02o4mpIR/SwW/tRnZlg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/win32-arm64@1.8.0': + resolution: {integrity: sha512-QvhtDAU9bBdC2m5xO+ibKyMG4KZR44wB0vDbQ5YkQxJiuXrlleHLyz0+saFzVYQ/Fvc0QgIRTIwiVz9dzxidVw==} + cpu: [arm64] + os: [win32] + + '@oxlint/win32-x64@1.8.0': + resolution: {integrity: sha512-veXJXgF905UOvuxtmvzM328b4Itm8Fyu+lUq4PagXOmyRScevaVUXq6++ui3A/Gxd8yo0SHspHCbYkpuvJkXqQ==} + cpu: [x64] + os: [win32] + + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + engines: {node: '>= 10.0.0'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/pluginutils@1.0.0-rc.13': + resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==} + + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + cpu: [x64] + os: [win32] + + '@sxzz/popperjs-es@2.11.8': + resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==} + + '@tailwindcss/forms@0.5.11': + resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' + + '@tailwindcss/node@4.2.4': + resolution: {integrity: sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==} + + '@tailwindcss/oxide-android-arm64@4.2.4': + resolution: {integrity: sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + resolution: {integrity: sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.4': + resolution: {integrity: sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + resolution: {integrity: sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + resolution: {integrity: sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + resolution: {integrity: sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + resolution: {integrity: sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + resolution: {integrity: sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.4': + resolution: {integrity: sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==} + engines: {node: '>= 20'} + + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tailwindcss/vite@4.2.4': + resolution: {integrity: sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@tinymce/tinymce-vue@6.3.0': + resolution: {integrity: sha512-DSP8Jhd3XqCCliTnusfbmz3D8GqQ4iRzkc4aadYHDcJPVjkaqopJ61McOdH82CSy599vGLkPjGzqJYWJkRMiUA==} + peerDependencies: + tinymce: ^8.0.0 || ^7.0.0 || ^6.0.0 || ^5.5.1 + vue: ^3.0.0 + peerDependenciesMeta: + tinymce: + optional: true + + '@transloadit/prettier-bytes@0.0.7': + resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/event-emitter@0.3.5': + resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/web-bluetooth@0.0.20': + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@uppy/companion-client@2.2.2': + resolution: {integrity: sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==} + + '@uppy/core@2.3.4': + resolution: {integrity: sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==} + + '@uppy/store-default@2.1.1': + resolution: {integrity: sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==} + + '@uppy/utils@4.1.3': + resolution: {integrity: sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==} + + '@uppy/xhr-upload@2.1.3': + resolution: {integrity: sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==} + peerDependencies: + '@uppy/core': ^2.3.3 + + '@vitejs/plugin-vue@6.0.6': + resolution: {integrity: sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@vue/babel-helper-vue-transform-on@1.5.0': + resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} + + '@vue/babel-plugin-jsx@1.5.0': + resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@1.5.0': + resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.32': + resolution: {integrity: sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==} + + '@vue/compiler-dom@3.5.32': + resolution: {integrity: sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==} + + '@vue/compiler-sfc@2.7.16': + resolution: {integrity: sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==} + + '@vue/compiler-sfc@3.5.32': + resolution: {integrity: sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==} + + '@vue/compiler-ssr@3.5.32': + resolution: {integrity: sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + + '@vue/devtools-core@8.1.1': + resolution: {integrity: sha512-bCCsSABp1/ot4j8xJEycM6Mtt2wbuucfByr6hMgjbYhrtlscOJypZKvy8f1FyWLYrLTchB5Qz216Lm92wfbq0A==} + peerDependencies: + vue: ^3.0.0 + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-kit@8.1.1': + resolution: {integrity: sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + + '@vue/devtools-shared@8.1.1': + resolution: {integrity: sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ==} + + '@vue/eslint-config-prettier@10.2.0': + resolution: {integrity: sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==} + peerDependencies: + eslint: '>= 8.21.0' + prettier: '>= 3.0.0' + + '@vue/reactivity@3.5.32': + resolution: {integrity: sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==} + + '@vue/runtime-core@3.5.32': + resolution: {integrity: sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==} + + '@vue/runtime-dom@3.5.32': + resolution: {integrity: sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==} + + '@vue/server-renderer@3.5.32': + resolution: {integrity: sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==} + peerDependencies: + vue: 3.5.32 + + '@vue/shared@3.5.32': + resolution: {integrity: sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==} + + '@vueuse/core@12.0.0': + resolution: {integrity: sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==} + + '@vueuse/core@13.9.0': + resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@12.0.0': + resolution: {integrity: sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==} + + '@vueuse/metadata@13.9.0': + resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==} + + '@vueuse/shared@12.0.0': + resolution: {integrity: sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==} + + '@vueuse/shared@13.9.0': + resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==} + peerDependencies: + vue: ^3.5.0 + + '@wangeditor/basic-modules@1.1.7': + resolution: {integrity: sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/code-highlight@1.0.3': + resolution: {integrity: sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/core@1.1.19': + resolution: {integrity: sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==} + peerDependencies: + '@uppy/core': ^2.1.1 + '@uppy/xhr-upload': ^2.0.3 + dom7: ^3.0.0 + is-hotkey: ^0.2.0 + lodash.camelcase: ^4.3.0 + lodash.clonedeep: ^4.5.0 + lodash.debounce: ^4.0.8 + lodash.foreach: ^4.5.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + lodash.toarray: ^4.4.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/editor-for-vue@5.1.12': + resolution: {integrity: sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==} + peerDependencies: + '@wangeditor/editor': '>=5.1.0' + vue: ^3.0.5 + + '@wangeditor/editor@5.1.23': + resolution: {integrity: sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==} + + '@wangeditor/list-module@1.0.5': + resolution: {integrity: sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/table-module@1.1.4': + resolution: {integrity: sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==} + peerDependencies: + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.isequal: ^4.5.0 + lodash.throttle: ^4.1.1 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/upload-image-module@1.0.2': + resolution: {integrity: sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==} + peerDependencies: + '@uppy/core': ^2.0.3 + '@uppy/xhr-upload': ^2.0.3 + '@wangeditor/basic-modules': 1.x + '@wangeditor/core': 1.x + dom7: ^3.0.0 + lodash.foreach: ^4.5.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + '@wangeditor/video-module@1.1.4': + resolution: {integrity: sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==} + peerDependencies: + '@uppy/core': ^2.1.4 + '@uppy/xhr-upload': ^2.0.7 + '@wangeditor/core': 1.x + dom7: ^3.0.0 + nanoid: ^3.2.0 + slate: ^0.72.0 + snabbdom: ^3.1.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.15.2: + resolution: {integrity: sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.10.20: + resolution: {integrity: sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001788: + resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color4bg@0.1.1: + resolution: {integrity: sha512-vkRRfqnbBM6p/OpQbjM1Yuy8sOwc56NuzE24b4gXFFRVwfwqVJ5b+e9U8qZ3FsH84EmJGodP+v5sB4hzyy0veg==} + + colorjs.io@0.5.2: + resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d@1.0.2: + resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} + engines: {node: '>=0.12'} + + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + + dom7@3.0.0: + resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + echarts@6.0.0: + resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==} + + electron-to-chromium@1.5.343: + resolution: {integrity: sha512-YHnQ3MXI08icvL9ZKnEBy05F2EQ8ob01UaMOuMbM8l+4UcAq6MPPbBTJBbsBUg3H8JeZNt+O4fjsoWth3p6IFg==} + + element-plus@2.13.7: + resolution: {integrity: sha512-XdHATFZOyzVFL1DaHQ90IOJQSg9UnSAV+bhDW+YB5UoZ0Hxs50mwqjqfwXkuwpSag+VXXizVcErBR6Movo5daw==} + peerDependencies: + vue: ^3.3.0 + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es5-ext@0.10.64: + resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + + es6-symbol@3.1.4: + resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} + engines: {node: '>=0.12'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-oxlint@1.8.0: + resolution: {integrity: sha512-C6QNZqB8ANc7rLI2yVEHOTV68uyYdACCasR54sfbXmsxac11aoUzzAIM2i6CBumqOz2qKEEcCZBNMRv++/hhFA==} + + eslint-plugin-prettier@5.5.5: + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-vue@10.3.0: + resolution: {integrity: sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + vue-eslint-parser: ^10.0.0 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esniff@2.0.1: + resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} + engines: {node: '>=0.10'} + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + + i18next@20.6.1: + resolution: {integrity: sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} + + immutable@5.1.5: + resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hotkey@0.2.0: + resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@4.0.0: + resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} + engines: {node: ^18.17.0 || >=20.5.0} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.foreach@4.5.0: + resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + lodash.toarray@4.4.0: + resolution: {integrity: sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-match@1.0.2: + resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + namespace-emitter@2.0.1: + resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + npm-normalize-package-bin@4.0.0: + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-run-all2@8.0.4: + resolution: {integrity: sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==} + engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'} + hasBin: true + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + oxlint@1.8.0: + resolution: {integrity: sha512-kDC3zuplBM35GbrZ/3rRdDrZ6unpUkUjM8P3VSbyLgaYh2xZeg0TLLDbYALNAUyChVonNafXzgHZmbwnHfrTRg==} + engines: {node: '>=8.*'} + hasBin: true + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pdfjs-dist@4.10.38: + resolution: {integrity: sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==} + engines: {node: '>=20'} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pinia@3.0.4: + resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} + peerDependencies: + typescript: '>=4.5.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.29.1: + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + read-package-json-fast@4.0.0: + resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} + engines: {node: ^18.17.0 || >=20.5.0} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + sass-embedded-all-unknown@1.99.0: + resolution: {integrity: sha512-qPIRG8Uhjo6/OKyAKixTnwMliTz+t9K6Duk0mx5z+K7n0Ts38NSJz2sjDnc7cA/8V9Lb3q09H38dZ1CLwD+ssw==} + cpu: ['!arm', '!arm64', '!riscv64', '!x64'] + + sass-embedded-android-arm64@1.99.0: + resolution: {integrity: sha512-fNHhdnP23yqqieCbAdym4N47AleSwjbNt6OYIYx4DdACGdtERjQB4iOX/TaKsW034MupfF7SjnAAK8w7Ptldtg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + + sass-embedded-android-arm@1.99.0: + resolution: {integrity: sha512-EHvJ0C7/VuP78Qr6f8gIUVUmCqIorEQpw2yp3cs3SMg02ZuumlhjXvkTcFBxHmFdFR23vTNk1WnhY6QSeV1nFQ==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + + sass-embedded-android-riscv64@1.99.0: + resolution: {integrity: sha512-4zqDFRvgGDTL5vTHuIhRxUpXFoh0Cy7Gm5Ywk19ASd8Settmd14YdPRZPmMxfgS1GH292PofV1fq1ifiSEJWBw==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [android] + + sass-embedded-android-x64@1.99.0: + resolution: {integrity: sha512-Uk53k/dGYt04RjOL4gFjZ0Z9DH9DKh8IA8WsXUkNqsxerAygoy3zqRBS2zngfE9K2jiOM87q+1R1p87ory9oQQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + + sass-embedded-darwin-arm64@1.99.0: + resolution: {integrity: sha512-u61/7U3IGLqoO6gL+AHeiAtlTPFwJK1+964U8gp45ZN0hzh1yrARf5O1mivXv8NnNgJvbG2wWJbiNZP0lG/lTg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + + sass-embedded-darwin-x64@1.99.0: + resolution: {integrity: sha512-j/kkk/NcXdIameLezSfXjgCiBkVcA+G60AXrX768/3g0miK1g7M9dj7xOhCb1i7/wQeiEI3rw2LLuO63xRIn4A==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + + sass-embedded-linux-arm64@1.99.0: + resolution: {integrity: sha512-btNcFpItcB56L40n8hDeL7sRSMLDXQ56nB5h2deddJx1n60rpKSElJmkaDGHtpkrY+CTtDRV0FZDjHeTJddYew==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + libc: glibc + + sass-embedded-linux-arm@1.99.0: + resolution: {integrity: sha512-d4IjJZrX2+AwB2YCy1JySwdptJECNP/WfAQLUl8txI3ka8/d3TUI155GtelnoZUkio211PwIeFvvAeZ9RXPQnw==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + libc: glibc + + sass-embedded-linux-musl-arm64@1.99.0: + resolution: {integrity: sha512-Hi2bt/IrM5P4FBKz6EcHAlniwfpoz9mnTdvSd58y+avA3SANM76upIkAdSayA8ZGwyL3gZokru1AKDPF9lJDNw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + libc: musl + + sass-embedded-linux-musl-arm@1.99.0: + resolution: {integrity: sha512-2gvHOupgIw3ytatXT4nFUow71LFbuOZPEwG+HUzcNQDH8ue4Ez8cr03vsv5MDv3lIjOKcXwDvWD980t18MwkoQ==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + libc: musl + + sass-embedded-linux-musl-riscv64@1.99.0: + resolution: {integrity: sha512-mKqGvVaJ9rHMqyZsF0kikQe4NO0f4osb67+X6nLhBiVDKvyazQHJ3zJQreNefIE36yL2sjHIclSB//MprzaQDg==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + libc: musl + + sass-embedded-linux-musl-x64@1.99.0: + resolution: {integrity: sha512-huhgOMmOc30r7CH7qbRbT9LerSEGSnWuS4CYNOskr9BvNeQp4dIneFufNRGZ7hkOAxUM8DglxIZJN/cyAT95Ew==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + libc: musl + + sass-embedded-linux-riscv64@1.99.0: + resolution: {integrity: sha512-mevFPIFAVhrH90THifxLfOntFmHtcEKOcdWnep2gJ0X4DVva4AiVIRlQe/7w9JFx5+gnDRE1oaJJkzuFUuYZsA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + libc: glibc + + sass-embedded-linux-x64@1.99.0: + resolution: {integrity: sha512-9k7IkULqIZdCIVt4Mboryt6vN8Mjmm3EhI1P3mClU5y5i3wLK5ExC3cbVWk047KsID/fvB1RLslqghXJx5BoxA==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + libc: glibc + + sass-embedded-unknown-all@1.99.0: + resolution: {integrity: sha512-P7MxiUtL/XzGo3PX0CaB8lNNEFLQWKikPA8pbKytx9ZCLZSDkt2NJcdAbblB/sqMs4AV3EK2NadV8rI/diq3xg==} + os: ['!android', '!darwin', '!linux', '!win32'] + + sass-embedded-win32-arm64@1.99.0: + resolution: {integrity: sha512-8whpsW7S+uO8QApKfQuc36m3P9EISzbVZOgC79goob4qGy09u8Gz/rYvw8h1prJDSjltpHGhOzBE6LDz7WvzVw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + + sass-embedded-win32-x64@1.99.0: + resolution: {integrity: sha512-ipuOv1R2K4MHeuCEAZGpuUbAgma4gb0sdacyrTjJtMOy/OY9UvWfVlwErdB09KIkp4fPDpQJDJfvYN6bC8jeNg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + + sass-embedded@1.99.0: + resolution: {integrity: sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg==} + engines: {node: '>=16.0.0'} + hasBin: true + + sass@1.99.0: + resolution: {integrity: sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==} + engines: {node: '>=14.0.0'} + hasBin: true + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + slate-history@0.66.0: + resolution: {integrity: sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==} + peerDependencies: + slate: '>=0.65.3' + + slate@0.72.8: + resolution: {integrity: sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==} + + snabbdom@3.6.3: + resolution: {integrity: sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==} + engines: {node: '>=12.17.0'} + + sortablejs@1.14.0: + resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + ssr-window@3.0.0: + resolution: {integrity: sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + sync-child-process@1.0.2: + resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} + engines: {node: '>=16.0.0'} + + sync-message-port@1.2.0: + resolution: {integrity: sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==} + engines: {node: '>=16.0.0'} + + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + + tailwindcss@4.2.4: + resolution: {integrity: sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinymce@8.4.0: + resolution: {integrity: sha512-Q/0/iLycB7hgSkOBoRqfceOz/7Obs9zIo0DwTRuvrReLxb6qdz/1vI7aVJMcW2BAuW80QqZiTyYdVM2amBkqIg==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type@2.7.3: + resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + unimport@4.2.0: + resolution: {integrity: sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==} + engines: {node: '>=18.12.0'} + + unplugin-auto-import@19.3.0: + resolution: {integrity: sha512-iIi0u4Gq2uGkAOGqlPJOAMI8vocvjh1clGTfSK4SOrJKrt+tirrixo/FjgBwXQNNdS7ofcr7OxzmOb/RjWxeEQ==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': ^3.2.2 + '@vueuse/core': '*' + peerDependenciesMeta: + '@nuxt/kit': + optional: true + '@vueuse/core': + optional: true + + unplugin-utils@0.2.5: + resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} + engines: {node: '>=18.12.0'} + + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + + unplugin-vue-components@28.8.0: + resolution: {integrity: sha512-2Q6ZongpoQzuXDK0ZsVzMoshH0MWZQ1pzVL538G7oIDKRTVzHjppBDS8aB99SADGHN3lpGU7frraCG6yWNoL5Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/parser': ^7.15.8 + '@nuxt/kit': ^3.2.2 || ^4.0.0 + vue: 2 || 3 + peerDependenciesMeta: + '@babel/parser': + optional: true + '@nuxt/kit': + optional: true + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + + vite-dev-rpc@1.1.0: + resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} + peerDependencies: + vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 + + vite-hot-client@2.1.0: + resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} + peerDependencies: + vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + + vite-plugin-inspect@11.3.3: + resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} + engines: {node: '>=14'} + peerDependencies: + '@nuxt/kit': '*' + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + '@nuxt/kit': + optional: true + + vite-plugin-vue-devtools@8.1.1: + resolution: {integrity: sha512-9qTpOmZ2vHpvlI9hdVXAQ1Ry4I8GcBArU7aPi0qfIaV7fQIXy0L1nb6X4mFY2Gw0dYshHuLbIl0Ulb572SCjsQ==} + engines: {node: '>=v14.21.3'} + peerDependencies: + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + + vite-plugin-vue-inspector@5.4.0: + resolution: {integrity: sha512-Iq/024CydcE46FZqWPU4t4lw4uYOdLnFSO1RNxJVt2qY9zxIjmnkBqhHnYaReWM82kmNnaXs7OkfgRrV2GEjyw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + vite@7.3.2: + resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vue-component-type-helpers@3.2.7: + resolution: {integrity: sha512-+gPp5YGmhfsj1IN+xUo7y0fb4clfnOiiUA39y07yW1VzCRjzVgwLbtmdWlghh7mXrPsEaYc7rrIir/HT6C8vYQ==} + + vue-eslint-parser@10.4.0: + resolution: {integrity: sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + + vue-json-editor@1.4.3: + resolution: {integrity: sha512-st9HdXBgCnyEmmfWrZQiKzp4KuYXzmYVUNDn5h6Fa18MrrGS1amnyUFyv7hQFsNBDW27B7BKkdGOqszYT1srAg==} + engines: {node: '>= 4.0.0', npm: '>= 3.0.0'} + + vue-pdf-embed@2.1.4: + resolution: {integrity: sha512-rZuRpQ4kJXKXCdZBCg3WZcYfrhDMJElcJQsS1V8KlJICDtFzzAzeDDSJwQU89Dx447Dv018P3zj/4UiAjBwvyg==} + peerDependencies: + vue: ^3.3.0 + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue@2.7.16: + resolution: {integrity: sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==} + deprecated: Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details. + + vue@3.5.32: + resolution: {integrity: sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vuedraggable@4.1.0: + resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} + peerDependencies: + vue: ^3.0.1 + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + wildcard@1.1.2: + resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zrender@6.0.0: + resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bufbuild/protobuf@2.11.0': {} + + '@ctrl/tinycolor@4.2.0': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.32)': + dependencies: + vue: 3.5.32 + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + dependencies: + eslint: 9.39.4(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/utils@0.2.11': {} + + '@heroicons/vue@2.2.0(vue@3.5.32)': + dependencies: + vue: 3.5.32 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/canvas-android-arm64@0.1.99': + optional: true + + '@napi-rs/canvas-darwin-arm64@0.1.99': + optional: true + + '@napi-rs/canvas-darwin-x64@0.1.99': + optional: true + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.99': + optional: true + + '@napi-rs/canvas-linux-arm64-gnu@0.1.99': + optional: true + + '@napi-rs/canvas-linux-arm64-musl@0.1.99': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.99': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@0.1.99': + optional: true + + '@napi-rs/canvas-linux-x64-musl@0.1.99': + optional: true + + '@napi-rs/canvas-win32-arm64-msvc@0.1.99': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@0.1.99': + optional: true + + '@napi-rs/canvas@0.1.99': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 0.1.99 + '@napi-rs/canvas-darwin-arm64': 0.1.99 + '@napi-rs/canvas-darwin-x64': 0.1.99 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.99 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.99 + '@napi-rs/canvas-linux-arm64-musl': 0.1.99 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.99 + '@napi-rs/canvas-linux-x64-gnu': 0.1.99 + '@napi-rs/canvas-linux-x64-musl': 0.1.99 + '@napi-rs/canvas-win32-arm64-msvc': 0.1.99 + '@napi-rs/canvas-win32-x64-msvc': 0.1.99 + optional: true + + '@oxlint/darwin-arm64@1.8.0': + optional: true + + '@oxlint/darwin-x64@1.8.0': + optional: true + + '@oxlint/linux-arm64-gnu@1.8.0': + optional: true + + '@oxlint/linux-arm64-musl@1.8.0': + optional: true + + '@oxlint/linux-x64-gnu@1.8.0': + optional: true + + '@oxlint/linux-x64-musl@1.8.0': + optional: true + + '@oxlint/win32-arm64@1.8.0': + optional: true + + '@oxlint/win32-x64@1.8.0': + optional: true + + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.4 + 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 + optional: true + + '@pkgr/core@0.2.9': {} + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/pluginutils@1.0.0-rc.13': {} + + '@rollup/rollup-android-arm-eabi@4.60.2': + optional: true + + '@rollup/rollup-android-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-x64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + + '@sxzz/popperjs-es@2.11.8': {} + + '@tailwindcss/forms@0.5.11(tailwindcss@4.2.4)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 4.2.4 + + '@tailwindcss/node@4.2.4': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.4 + + '@tailwindcss/oxide-android-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.4': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.4': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.4': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.4': + optional: true + + '@tailwindcss/oxide@4.2.4': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-arm64': 4.2.4 + '@tailwindcss/oxide-darwin-x64': 4.2.4 + '@tailwindcss/oxide-freebsd-x64': 4.2.4 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.4 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.4 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.4 + '@tailwindcss/oxide-linux-x64-musl': 4.2.4 + '@tailwindcss/oxide-wasm32-wasi': 4.2.4 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 + + '@tailwindcss/typography@0.5.19(tailwindcss@4.2.4)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 4.2.4 + + '@tailwindcss/vite@4.2.4(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0))': + dependencies: + '@tailwindcss/node': 4.2.4 + '@tailwindcss/oxide': 4.2.4 + tailwindcss: 4.2.4 + vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + + '@tinymce/tinymce-vue@6.3.0(tinymce@8.4.0)(vue@3.5.32)': + dependencies: + vue: 3.5.32 + optionalDependencies: + tinymce: 8.4.0 + + '@transloadit/prettier-bytes@0.0.7': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/event-emitter@0.3.5': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.24 + + '@types/lodash@4.17.24': {} + + '@types/web-bluetooth@0.0.20': {} + + '@types/web-bluetooth@0.0.21': {} + + '@uppy/companion-client@2.2.2': + dependencies: + '@uppy/utils': 4.1.3 + namespace-emitter: 2.0.1 + + '@uppy/core@2.3.4': + dependencies: + '@transloadit/prettier-bytes': 0.0.7 + '@uppy/store-default': 2.1.1 + '@uppy/utils': 4.1.3 + lodash.throttle: 4.1.1 + mime-match: 1.0.2 + namespace-emitter: 2.0.1 + nanoid: 3.3.11 + preact: 10.29.1 + + '@uppy/store-default@2.1.1': {} + + '@uppy/utils@4.1.3': + dependencies: + lodash.throttle: 4.1.1 + + '@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4)': + dependencies: + '@uppy/companion-client': 2.2.2 + '@uppy/core': 2.3.4 + '@uppy/utils': 4.1.3 + nanoid: 3.3.11 + + '@vitejs/plugin-vue@6.0.6(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0))(vue@3.5.32)': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.13 + vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + vue: 3.5.32 + + '@vue/babel-helper-vue-transform-on@1.5.0': {} + + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.0)': + dependencies: + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@vue/babel-helper-vue-transform-on': 1.5.0 + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.0) + '@vue/shared': 3.5.32 + optionalDependencies: + '@babel/core': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.29.0)': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/parser': 7.29.2 + '@vue/compiler-sfc': 3.5.32 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.32': + dependencies: + '@babel/parser': 7.29.2 + '@vue/shared': 3.5.32 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.32': + dependencies: + '@vue/compiler-core': 3.5.32 + '@vue/shared': 3.5.32 + + '@vue/compiler-sfc@2.7.16': + dependencies: + '@babel/parser': 7.29.2 + postcss: 8.5.10 + source-map: 0.6.1 + optionalDependencies: + prettier: 2.8.8 + + '@vue/compiler-sfc@3.5.32': + dependencies: + '@babel/parser': 7.29.2 + '@vue/compiler-core': 3.5.32 + '@vue/compiler-dom': 3.5.32 + '@vue/compiler-ssr': 3.5.32 + '@vue/shared': 3.5.32 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.10 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.32': + dependencies: + '@vue/compiler-dom': 3.5.32 + '@vue/shared': 3.5.32 + + '@vue/devtools-api@6.6.4': {} + + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-core@8.1.1(vue@3.5.32)': + dependencies: + '@vue/devtools-kit': 8.1.1 + '@vue/devtools-shared': 8.1.1 + vue: 3.5.32 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-kit@8.1.1': + dependencies: + '@vue/devtools-shared': 8.1.1 + birpc: 2.9.0 + hookable: 5.5.3 + perfect-debounce: 2.1.0 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/devtools-shared@8.1.1': {} + + '@vue/eslint-config-prettier@10.2.0(eslint@9.39.4(jiti@2.6.1))(prettier@3.6.2)': + dependencies: + eslint: 9.39.4(jiti@2.6.1) + eslint-config-prettier: 10.1.8(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.6.2) + prettier: 3.6.2 + transitivePeerDependencies: + - '@types/eslint' + + '@vue/reactivity@3.5.32': + dependencies: + '@vue/shared': 3.5.32 + + '@vue/runtime-core@3.5.32': + dependencies: + '@vue/reactivity': 3.5.32 + '@vue/shared': 3.5.32 + + '@vue/runtime-dom@3.5.32': + dependencies: + '@vue/reactivity': 3.5.32 + '@vue/runtime-core': 3.5.32 + '@vue/shared': 3.5.32 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.32(vue@3.5.32)': + dependencies: + '@vue/compiler-ssr': 3.5.32 + '@vue/shared': 3.5.32 + vue: 3.5.32 + + '@vue/shared@3.5.32': {} + + '@vueuse/core@12.0.0': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 12.0.0 + '@vueuse/shared': 12.0.0 + vue: 3.5.32 + transitivePeerDependencies: + - typescript + + '@vueuse/core@13.9.0(vue@3.5.32)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 13.9.0 + '@vueuse/shared': 13.9.0(vue@3.5.32) + vue: 3.5.32 + + '@vueuse/metadata@12.0.0': {} + + '@vueuse/metadata@13.9.0': {} + + '@vueuse/shared@12.0.0': + dependencies: + vue: 3.5.32 + transitivePeerDependencies: + - typescript + + '@vueuse/shared@13.9.0(vue@3.5.32)': + dependencies: + vue: 3.5.32 + + '@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-url: 1.2.4 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/code-highlight@1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + prismjs: 1.30.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@types/event-emitter': 0.3.5 + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + dom7: 3.0.0 + event-emitter: 0.3.5 + html-void-elements: 2.0.1 + i18next: 20.6.1 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + scroll-into-view-if-needed: 2.2.31 + slate: 0.72.8 + slate-history: 0.66.0(slate@0.72.8) + snabbdom: 3.6.3 + + '@wangeditor/editor-for-vue@5.1.12(@wangeditor/editor@5.1.23)(vue@3.5.32)': + dependencies: + '@wangeditor/editor': 5.1.23 + vue: 3.5.32 + + '@wangeditor/editor@5.1.23': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/code-highlight': 1.0.3(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/list-module': 1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/table-module': 1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/upload-image-module': 1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/video-module': 1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + is-hotkey: 0.2.0 + lodash.camelcase: 4.3.0 + lodash.clonedeep: 4.5.0 + lodash.debounce: 4.0.8 + lodash.foreach: 4.5.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + lodash.toarray: 4.4.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/list-module@1.0.5(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/table-module@1.1.4(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.isequal: 4.5.0 + lodash.throttle: 4.1.1 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/upload-image-module@1.0.2(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/basic-modules@1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.foreach@4.5.0)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/basic-modules': 1.1.7(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(lodash.throttle@4.1.1)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + lodash.foreach: 4.5.0 + slate: 0.72.8 + snabbdom: 3.6.3 + + '@wangeditor/video-module@1.1.4(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(@wangeditor/core@1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3))(dom7@3.0.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3)': + dependencies: + '@uppy/core': 2.3.4 + '@uppy/xhr-upload': 2.1.3(@uppy/core@2.3.4) + '@wangeditor/core': 1.1.19(@uppy/core@2.3.4)(@uppy/xhr-upload@2.1.3(@uppy/core@2.3.4))(dom7@3.0.0)(is-hotkey@0.2.0)(lodash.camelcase@4.3.0)(lodash.clonedeep@4.5.0)(lodash.debounce@4.0.8)(lodash.foreach@4.5.0)(lodash.isequal@4.5.0)(lodash.throttle@4.1.1)(lodash.toarray@4.4.0)(nanoid@3.3.11)(slate@0.72.8)(snabbdom@3.6.3) + dom7: 3.0.0 + nanoid: 3.3.11 + slate: 0.72.8 + snabbdom: 3.6.3 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + ansis@4.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + argparse@2.0.1: {} + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + autoprefixer@10.5.0(postcss@8.5.10): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001788 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.10 + postcss-value-parser: 4.2.0 + + axios@1.15.2: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.5 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.10.20: {} + + binary-extensions@2.3.0: {} + + birpc@2.9.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.20 + caniuse-lite: 1.0.30001788 + electron-to-chromium: 1.5.343 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + caniuse-lite@1.0.30001788: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + optional: true + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color4bg@0.1.1: {} + + colorjs.io@0.5.2: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + convert-source-map@2.0.0: {} + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + d@1.0.2: + dependencies: + es5-ext: 0.10.64 + type: 2.7.3 + + dayjs@1.11.20: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + deep-is@0.1.4: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + dijkstrajs@1.0.3: {} + + dom7@3.0.0: + dependencies: + ssr-window: 3.0.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + echarts@6.0.0: + dependencies: + tslib: 2.3.0 + zrender: 6.0.0 + + electron-to-chromium@1.5.343: {} + + element-plus@2.13.7(vue@3.5.32): + dependencies: + '@ctrl/tinycolor': 4.2.0 + '@element-plus/icons-vue': 2.3.2(vue@3.5.32) + '@floating-ui/dom': 1.7.6 + '@popperjs/core': '@sxzz/popperjs-es@2.11.8' + '@types/lodash': 4.17.24 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 12.0.0 + async-validator: 4.2.5 + dayjs: 1.11.20 + lodash: 4.18.1 + lodash-es: 4.18.1 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.32 + vue-component-type-helpers: 3.2.7 + transitivePeerDependencies: + - typescript + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + entities@7.0.1: {} + + error-stack-parser-es@1.0.5: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.3 + + es5-ext@0.10.64: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.4 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + es6-symbol: 3.1.4 + + es6-symbol@3.1.4: + dependencies: + d: 1.0.2 + ext: 1.7.0 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): + dependencies: + eslint: 9.39.4(jiti@2.6.1) + + eslint-plugin-oxlint@1.8.0: + dependencies: + jsonc-parser: 3.3.1 + + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.6.2): + dependencies: + eslint: 9.39.4(jiti@2.6.1) + prettier: 3.6.2 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.39.4(jiti@2.6.1)) + + eslint-plugin-vue@10.3.0(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + eslint: 9.39.4(jiti@2.6.1) + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.4 + vue-eslint-parser: 10.4.0(eslint@9.39.4(jiti@2.6.1)) + xml-name-validator: 4.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + esniff@2.0.1: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + event-emitter: 0.3.5 + type: 2.7.3 + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.2 + es5-ext: 0.10.64 + + exsolve@1.0.8: {} + + ext@1.7.0: + dependencies: + type: 2.7.3 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + follow-redirects@1.16.0: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + + fraction.js@5.3.4: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.3 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + hookable@5.5.3: {} + + html-void-elements@2.0.1: {} + + i18next@20.6.1: + dependencies: + '@babel/runtime': 7.29.2 + + ignore@5.3.2: {} + + immer@9.0.21: {} + + immutable@5.1.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hotkey@0.2.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-number@7.0.0: {} + + is-plain-object@5.0.0: {} + + is-url@1.2.4: {} + + is-what@5.5.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + isexe@3.1.5: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@4.0.0: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.2 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.18.1: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.18.1 + lodash-es: 4.18.1 + + lodash.camelcase@4.3.0: {} + + lodash.clonedeep@4.5.0: {} + + lodash.debounce@4.0.8: {} + + lodash.foreach@4.5.0: {} + + lodash.isequal@4.5.0: {} + + lodash.merge@4.6.2: {} + + lodash.throttle@4.1.1: {} + + lodash.toarray@4.4.0: {} + + lodash@4.18.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + marked@16.4.2: {} + + math-intrinsics@1.1.0: {} + + memoize-one@6.0.0: {} + + memorystream@0.3.1: {} + + mime-db@1.52.0: {} + + mime-match@1.0.2: + dependencies: + wildcard: 1.1.2 + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mini-svg-data-uri@1.4.4: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + mitt@3.0.1: {} + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + namespace-emitter@2.0.1: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + next-tick@1.1.0: {} + + node-addon-api@7.1.1: + optional: true + + node-releases@2.0.38: {} + + normalize-path@3.0.0: {} + + normalize-wheel-es@1.2.0: {} + + npm-normalize-package-bin@4.0.0: {} + + npm-run-all2@8.0.4: + dependencies: + ansi-styles: 6.2.3 + cross-spawn: 7.0.6 + memorystream: 0.3.1 + picomatch: 4.0.4 + pidtree: 0.6.0 + read-package-json-fast: 4.0.0 + shell-quote: 1.8.3 + which: 5.0.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + ohash@2.0.11: {} + + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + oxlint@1.8.0: + optionalDependencies: + '@oxlint/darwin-arm64': 1.8.0 + '@oxlint/darwin-x64': 1.8.0 + '@oxlint/linux-arm64-gnu': 1.8.0 + '@oxlint/linux-arm64-musl': 1.8.0 + '@oxlint/linux-x64-gnu': 1.8.0 + '@oxlint/linux-x64-musl': 1.8.0 + '@oxlint/win32-arm64': 1.8.0 + '@oxlint/win32-x64': 1.8.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + pdfjs-dist@4.10.38: + optionalDependencies: + '@napi-rs/canvas': 0.1.99 + + perfect-debounce@1.0.0: {} + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pidtree@0.6.0: {} + + pinia@3.0.4(vue@3.5.32): + dependencies: + '@vue/devtools-api': 7.7.9 + vue: 3.5.32 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + pngjs@5.0.0: {} + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.29.1: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@2.8.8: + optional: true + + prettier@3.6.2: {} + + prismjs@1.30.0: {} + + proxy-from-env@2.1.0: {} + + punycode@2.3.1: {} + + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + + quansync@0.2.11: {} + + read-package-json-fast@4.0.0: + dependencies: + json-parse-even-better-errors: 4.0.0 + npm-normalize-package-bin: 4.0.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + readdirp@4.1.2: + optional: true + + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + + resolve-from@4.0.0: {} + + rfdc@1.4.1: {} + + rollup@4.60.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 + fsevents: 2.3.3 + + run-applescript@7.1.0: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + sass-embedded-all-unknown@1.99.0: + dependencies: + sass: 1.99.0 + optional: true + + sass-embedded-android-arm64@1.99.0: + optional: true + + sass-embedded-android-arm@1.99.0: + optional: true + + sass-embedded-android-riscv64@1.99.0: + optional: true + + sass-embedded-android-x64@1.99.0: + optional: true + + sass-embedded-darwin-arm64@1.99.0: + optional: true + + sass-embedded-darwin-x64@1.99.0: + optional: true + + sass-embedded-linux-arm64@1.99.0: + optional: true + + sass-embedded-linux-arm@1.99.0: + optional: true + + sass-embedded-linux-musl-arm64@1.99.0: + optional: true + + sass-embedded-linux-musl-arm@1.99.0: + optional: true + + sass-embedded-linux-musl-riscv64@1.99.0: + optional: true + + sass-embedded-linux-musl-x64@1.99.0: + optional: true + + sass-embedded-linux-riscv64@1.99.0: + optional: true + + sass-embedded-linux-x64@1.99.0: + optional: true + + sass-embedded-unknown-all@1.99.0: + dependencies: + sass: 1.99.0 + optional: true + + sass-embedded-win32-arm64@1.99.0: + optional: true + + sass-embedded-win32-x64@1.99.0: + optional: true + + sass-embedded@1.99.0: + dependencies: + '@bufbuild/protobuf': 2.11.0 + colorjs.io: 0.5.2 + immutable: 5.1.5 + rxjs: 7.8.2 + supports-color: 8.1.1 + sync-child-process: 1.0.2 + varint: 6.0.0 + optionalDependencies: + sass-embedded-all-unknown: 1.99.0 + sass-embedded-android-arm: 1.99.0 + sass-embedded-android-arm64: 1.99.0 + sass-embedded-android-riscv64: 1.99.0 + sass-embedded-android-x64: 1.99.0 + sass-embedded-darwin-arm64: 1.99.0 + sass-embedded-darwin-x64: 1.99.0 + sass-embedded-linux-arm: 1.99.0 + sass-embedded-linux-arm64: 1.99.0 + sass-embedded-linux-musl-arm: 1.99.0 + sass-embedded-linux-musl-arm64: 1.99.0 + sass-embedded-linux-musl-riscv64: 1.99.0 + sass-embedded-linux-musl-x64: 1.99.0 + sass-embedded-linux-riscv64: 1.99.0 + sass-embedded-linux-x64: 1.99.0 + sass-embedded-unknown-all: 1.99.0 + sass-embedded-win32-arm64: 1.99.0 + sass-embedded-win32-x64: 1.99.0 + + sass@1.99.0: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.5 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + optional: true + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + scule@1.3.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + set-blocking@2.0.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + slate-history@0.66.0(slate@0.72.8): + dependencies: + is-plain-object: 5.0.0 + slate: 0.72.8 + + slate@0.72.8: + dependencies: + immer: 9.0.21 + is-plain-object: 5.0.0 + tiny-warning: 1.0.3 + + snabbdom@3.6.3: {} + + sortablejs@1.14.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + speakingurl@14.0.1: {} + + ssr-window@3.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@3.1.1: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + sync-child-process@1.0.2: + dependencies: + sync-message-port: 1.2.0 + + sync-message-port@1.2.0: {} + + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + + tailwindcss@4.2.4: {} + + tapable@2.3.3: {} + + tiny-warning@1.0.3: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinymce@8.4.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + totalist@3.0.1: {} + + tslib@2.3.0: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type@2.7.3: {} + + ufo@1.6.3: {} + + unimport@4.2.0: + dependencies: + acorn: 8.16.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.2 + pathe: 2.0.3 + picomatch: 4.0.4 + pkg-types: 2.3.0 + scule: 1.3.0 + strip-literal: 3.1.0 + tinyglobby: 0.2.16 + unplugin: 2.3.11 + unplugin-utils: 0.2.5 + + unplugin-auto-import@19.3.0(@vueuse/core@13.9.0(vue@3.5.32)): + dependencies: + local-pkg: 1.1.2 + magic-string: 0.30.21 + picomatch: 4.0.4 + unimport: 4.2.0 + unplugin: 2.3.11 + unplugin-utils: 0.2.5 + optionalDependencies: + '@vueuse/core': 13.9.0(vue@3.5.32) + + unplugin-utils@0.2.5: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.4 + + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.4 + + unplugin-vue-components@28.8.0(@babel/parser@7.29.2)(vue@3.5.32): + dependencies: + chokidar: 3.6.0 + debug: 4.4.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.2 + tinyglobby: 0.2.16 + unplugin: 2.3.11 + unplugin-utils: 0.2.5 + vue: 3.5.32 + optionalDependencies: + '@babel/parser': 7.29.2 + transitivePeerDependencies: + - supports-color + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + varint@6.0.0: {} + + vite-dev-rpc@1.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)): + dependencies: + birpc: 2.9.0 + vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + vite-hot-client: 2.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)) + + vite-hot-client@2.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)): + dependencies: + vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + + vite-plugin-inspect@11.3.3(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)): + dependencies: + ansis: 4.2.0 + debug: 4.4.3 + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + vite-dev-rpc: 1.1.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)) + transitivePeerDependencies: + - supports-color + + vite-plugin-vue-devtools@8.1.1(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0))(vue@3.5.32): + dependencies: + '@vue/devtools-core': 8.1.1(vue@3.5.32) + '@vue/devtools-kit': 8.1.1 + '@vue/devtools-shared': 8.1.1 + sirv: 3.0.2 + vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + vite-plugin-inspect: 11.3.3(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)) + vite-plugin-vue-inspector: 5.4.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + - vue + + vite-plugin-vue-inspector@5.4.0(vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0) + '@vue/compiler-dom': 3.5.32 + kolorist: 1.8.0 + magic-string: 0.30.21 + vite: 7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0) + transitivePeerDependencies: + - supports-color + + vite@7.3.2(jiti@2.6.1)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.10 + rollup: 4.60.2 + tinyglobby: 0.2.16 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.32.0 + sass: 1.99.0 + sass-embedded: 1.99.0 + + vue-component-type-helpers@3.2.7: {} + + vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1)): + dependencies: + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + vue-json-editor@1.4.3: + dependencies: + vue: 2.7.16 + + vue-pdf-embed@2.1.4(vue@3.5.32): + dependencies: + pdfjs-dist: 4.10.38 + vue: 3.5.32 + + vue-router@4.6.4(vue@3.5.32): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.32 + + vue@2.7.16: + dependencies: + '@vue/compiler-sfc': 2.7.16 + csstype: 3.2.3 + + vue@3.5.32: + dependencies: + '@vue/compiler-dom': 3.5.32 + '@vue/compiler-sfc': 3.5.32 + '@vue/runtime-dom': 3.5.32 + '@vue/server-renderer': 3.5.32(vue@3.5.32) + '@vue/shared': 3.5.32 + + vuedraggable@4.1.0(vue@3.5.32): + dependencies: + sortablejs: 1.14.0 + vue: 3.5.32 + + webpack-virtual-modules@0.6.2: {} + + which-module@2.0.1: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@5.0.0: + dependencies: + isexe: 3.1.5 + + wildcard@1.1.2: {} + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + + xml-name-validator@4.0.0: {} + + y18n@4.0.3: {} + + yallist@3.1.1: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yocto-queue@0.1.0: {} + + zrender@6.0.0: + dependencies: + tslib: 2.3.0 diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..f7651e6 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..bb2dd69 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..48eced2 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/examples/csharp/demo.cs b/public/examples/csharp/demo.cs new file mode 100644 index 0000000..7eb9dc0 --- /dev/null +++ b/public/examples/csharp/demo.cs @@ -0,0 +1,282 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Net.Http; +using System.Threading.Tasks; + +public class Program +{ + // ==================== 配置区域 ==================== + // 请根据实际情况修改以下配置参数 + + // AES加密密钥 (16进制字符串,32位) + + // API接口配置 + private const string InterfaceName = "XXXXXXXX"; // 接口编号 + private const string AccessId = "XXXXXXXXXXX"; // 访问ID + private const string EncryptionKey = "XXXXXXXXXXXXXXXXXXX"; // 加密密钥 + private const string BaseUrl = "https://api.haiyudata.com"; // API基础URL + + // 测试数据配置 + private const string TestMobileNo = "13700000000"; // 测试手机号 + private const string TestIdCard = "XXXXXXXXXXXXX"; // 测试身份证号 + private const string TestName = "XXXXXXXX"; // 测试姓名 + private const string TestAuthDate = "20250318-20270318"; // 测试授权日期 + + // HTTP请求配置 + private const int RequestTimeout = 30000; // 请求超时时间(毫秒) + + // ==================== 主程序 ==================== + public static async Task Main() + { + Console.WriteLine("===== AES CBC 加密解密演示 ====="); + + // 1. 创建一个JSON对象 + var data = new + { + mobile_no = TestMobileNo + }; + + // 将JSON对象序列化为字符串 + string jsonString = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); + + Console.WriteLine("\n原始JSON:"); + Console.WriteLine(jsonString); + + // 2. 加密JSON + Console.WriteLine("\n开始加密..."); + string encryptedJson = Encrypt(jsonString); + Console.WriteLine($"加密成功! 加密后Base64:"); + Console.WriteLine(encryptedJson); + + // 3. 解密JSON + Console.WriteLine("\n开始解密..."); + string decryptedJson = Decrypt(encryptedJson); + Console.WriteLine($"解密成功! 解密后的原始JSON:"); + Console.WriteLine(decryptedJson); + + // 4. 验证加解密一致性 + Console.WriteLine("\n验证结果:"); + if (jsonString == decryptedJson) + { + Console.WriteLine("✅ 加解密前后内容完全一致"); + + // 验证原始数据是否可用 + Console.WriteLine("\n尝试解析解密后的JSON:"); + try + { + var deserializedObject = JsonSerializer.Deserialize(decryptedJson, typeof(object)); + Console.WriteLine($"✅ JSON解析成功,类型: {deserializedObject.GetType()}"); + } + catch (Exception ex) + { + Console.WriteLine($"❌ JSON解析失败: {ex.Message}"); + } + } + else + { + Console.WriteLine("❌ 加解密前后内容不一致!"); + Console.WriteLine("原始长度: " + jsonString.Length); + Console.WriteLine("解密后长度: " + decryptedJson.Length); + + // 找出第一个不同的位置 + for (int i = 0; i < Math.Min(jsonString.Length, decryptedJson.Length); i++) + { + if (jsonString[i] != decryptedJson[i]) + { + Console.WriteLine($"第一个差异在位置 {i}:"); + Console.WriteLine($"原始字符: '{jsonString[i]}' ({(int)jsonString[i]})"); + Console.WriteLine($"解密字符: '{decryptedJson[i]}' ({(int)decryptedJson[i]})"); + break; + } + } + } + + // 5. 演示API调用流程(可选) + Console.WriteLine("\n===== 演示API调用流程 ====="); + await DemonstrateApiCall(); + } + + // ==================== AES加密解密方法 ==================== + + /// + /// AES CBC 加密函数 + /// 使用PKCS7填充,随机IV,返回Base64编码的密文 + /// + /// 要加密的明文 + /// Base64编码的密文 + public static string Encrypt(string plainText) + { + byte[] key = HexToBytes(EncryptionKey); + using Aes aes = Aes.Create(); + aes.Key = key; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + aes.GenerateIV(); + byte[] iv = aes.IV; + + using ICryptoTransform encryptor = aes.CreateEncryptor(); + using MemoryStream ms = new(); + // 先写入IV + ms.Write(iv, 0, iv.Length); + + using (CryptoStream cs = new(ms, encryptor, CryptoStreamMode.Write)) + using (StreamWriter sw = new(cs)) + { + sw.Write(plainText); + } + + return Convert.ToBase64String(ms.ToArray()); + } + + /// + /// AES CBC 解密函数 + /// 从Base64密文中提取IV和密文数据,进行解密 + /// + /// Base64编码的密文 + /// 解密后的明文 + public static string Decrypt(string cipherText) + { + byte[] key = HexToBytes(EncryptionKey); + byte[] fullData = Convert.FromBase64String(cipherText); + + // 提取前16字节作为IV + byte[] iv = new byte[16]; + Buffer.BlockCopy(fullData, 0, iv, 0, iv.Length); + + // 实际密文数据 + byte[] cipherData = new byte[fullData.Length - 16]; + Buffer.BlockCopy(fullData, 16, cipherData, 0, cipherData.Length); + + using Aes aes = Aes.Create(); + aes.Key = key; + aes.IV = iv; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + + using ICryptoTransform decryptor = aes.CreateDecryptor(); + using MemoryStream ms = new(cipherData); + using CryptoStream cs = new(ms, decryptor, CryptoStreamMode.Read); + using StreamReader sr = new(cs); + return sr.ReadToEnd(); + } + + /// + /// 将16进制字符串转换为字节数组 + /// + /// 16进制字符串 + /// 字节数组 + private static byte[] HexToBytes(string hex) + { + byte[] bytes = new byte[hex.Length / 2]; + for (int i = 0; i < hex.Length; i += 2) + { + bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); + } + return bytes; + } + + // ==================== API调用演示方法 ==================== + + /// + /// 演示完整的API调用流程 + /// 包括:参数构建、加密、发送请求、接收响应、解密响应 + /// + private static async Task DemonstrateApiCall() + { + try + { + // 构建完整的API URL + string url = $"{BaseUrl}/api/v1/{InterfaceName}"; + + // 构建请求参数 + var apiParams = new + { + mobile_no = TestMobileNo, + id_card = TestIdCard, + auth_date = TestAuthDate, + name = TestName + }; + + // 将参数转换为JSON字符串并加密 + string jsonStr = JsonSerializer.Serialize(apiParams); + Console.WriteLine($"请求参数: {jsonStr}"); + + string encryptedData = Encrypt(jsonStr); + Console.WriteLine($"加密后的数据: {encryptedData}"); + + // 构建请求体 + var payload = new { data = encryptedData }; + string requestBody = JsonSerializer.Serialize(payload); + + Console.WriteLine($"发送请求到: {url}"); + + // 发送HTTP请求 + using (var httpClient = new HttpClient()) + { + // 设置请求头 + httpClient.DefaultRequestHeaders.Add("Access-Id", AccessId); + httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json"); + + // 设置超时时间 + httpClient.Timeout = TimeSpan.FromMilliseconds(RequestTimeout); + + var content = new StringContent(requestBody, Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync(url, content); + + Console.WriteLine($"响应状态码: {response.StatusCode}"); + + if (response.IsSuccessStatusCode) + { + string responseBody = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"API响应: {responseBody}"); + + // 解析响应 + try + { + var responseData = JsonSerializer.Deserialize(responseBody); + + if (responseData.TryGetProperty("code", out var codeElement)) + { + int code = codeElement.GetInt32(); + string message = responseData.TryGetProperty("message", out var msgElement) ? msgElement.GetString() : ""; + string encryptedResponse = responseData.TryGetProperty("data", out var dataElement) ? dataElement.GetString() : ""; + + Console.WriteLine($"API响应码: {code}"); + Console.WriteLine($"API消息: {message}"); + + if (code == 0 && !string.IsNullOrEmpty(encryptedResponse)) + { + // 解密响应数据 + try + { + string decryptedResponse = Decrypt(encryptedResponse); + Console.WriteLine($"解密后的响应: {decryptedResponse}"); + } + catch (Exception ex) + { + Console.WriteLine($"解密响应数据失败: {ex.Message}"); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"解析响应失败: {ex.Message}"); + } + } + else + { + Console.WriteLine($"请求失败: {response.StatusCode}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"API调用异常: {ex.Message}"); + } + } +} diff --git a/public/examples/go/demo.go b/public/examples/go/demo.go new file mode 100644 index 0000000..145f274 --- /dev/null +++ b/public/examples/go/demo.go @@ -0,0 +1,305 @@ +package main + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +// ==================== 配置区域 ==================== +// 请根据实际情况修改以下配置参数 + +const ( + // API接口配置 + InterfaceName = "XXXXXXXX" // 接口编号 + AccessID = "XXXXXXXXXXX" + EncryptionKey = "XXXXXXXXXXXXXXXXXXXXX" + BaseURL = "https://api.haiyudata.com" + + // 测试数据配置 + TestName = "XXXXXXXX" + TestIDCard = "XXXXXXXXXXXXX" + TestMobileNo = "XXXXXXXXXXXXXXXXXXXX" + TestAuthDate = "20250318-20270318" + + // HTTP请求配置 + RequestTimeout = 30 * time.Second +) + +// API响应结构体 +type APIResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data string `json:"data"` +} + +// 请求参数结构体 +type RequestParams struct { + MobileNo string `json:"mobile_no"` + IDCard string `json:"id_card"` + AuthDate string `json:"auth_date"` + Name string `json:"name"` +} + +// 请求载荷结构体 +type RequestPayload struct { + Data string `json:"data"` +} + +// 结果结构体 +type Result struct { + Code int `json:"code"` + Success bool `json:"success"` + Message string `json:"message"` + EncryptedResponse string `json:"encrypted_response"` + DecryptedResponse interface{} `json:"decrypted_response"` +} + +// ==================== AES加密解密方法 ==================== + +// AES CBC 加密函数,返回 Base64 +func aesEncrypt(plaintext, key string) (string, error) { + keyBytes, err := hex.DecodeString(key) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(keyBytes) + if err != nil { + return "", err + } + + // 生成随机IV + iv := make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + + // 填充数据 + paddedData := pkcs7Pad([]byte(plaintext), aes.BlockSize) + + // 加密 + ciphertext := make([]byte, len(iv)+len(paddedData)) + copy(ciphertext, iv) + + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext[len(iv):], paddedData) + + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +// AES CBC 解密函数,返回解密后的明文 +func aesDecrypt(encryptedText, key string) (string, error) { + keyBytes, err := hex.DecodeString(key) + if err != nil { + return "", err + } + + ciphertext, err := base64.StdEncoding.DecodeString(encryptedText) + if err != nil { + return "", err + } + + block, err := aes.NewCipher(keyBytes) + if err != nil { + return "", err + } + + if len(ciphertext) < aes.BlockSize { + return "", fmt.Errorf("密文太短") + } + + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + if len(ciphertext)%aes.BlockSize != 0 { + return "", fmt.Errorf("密文长度不是块大小的倍数") + } + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(ciphertext, ciphertext) + + // 去除填充 + unpaddedData, err := pkcs7Unpad(ciphertext) + if err != nil { + return "", err + } + + return string(unpaddedData), nil +} + +// PKCS7填充 +func pkcs7Pad(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padtext...) +} + +// PKCS7去除填充 +func pkcs7Unpad(data []byte) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, fmt.Errorf("数据为空") + } + unpadding := int(data[length-1]) + if unpadding > length { + return nil, fmt.Errorf("无效的填充") + } + return data[:length-unpadding], nil +} + +// ==================== API调用方法 ==================== + +// 调用API函数 +func callAPI(name, idCard, mobileNo, authDate string) *Result { + // 构建完整的API URL + url := fmt.Sprintf("%s/api/v1/%s", BaseURL, InterfaceName) + + // 构建请求参数 + params := RequestParams{ + MobileNo: mobileNo, + IDCard: idCard, + AuthDate: authDate, + Name: name, + } + + // 将参数转换为JSON字符串并加密 + jsonStr, err := json.Marshal(params) + if err != nil { + return &Result{ + Success: false, + Message: fmt.Sprintf("JSON序列化失败: %v", err), + } + } + + fmt.Printf("请求参数: %s\n", string(jsonStr)) + + encryptedData, err := aesEncrypt(string(jsonStr), EncryptionKey) + if err != nil { + return &Result{ + Success: false, + Message: fmt.Sprintf("加密失败: %v", err), + } + } + + fmt.Printf("加密后的数据: %s\n", encryptedData) + + // 构建请求载荷 + payload := RequestPayload{ + Data: encryptedData, + } + + // 序列化请求载荷 + requestBody, err := json.Marshal(payload) + if err != nil { + return &Result{ + Success: false, + Message: fmt.Sprintf("请求载荷序列化失败: %v", err), + } + } + + fmt.Printf("发送请求到: %s\n", url) + + // 发送HTTP请求 + client := &http.Client{ + Timeout: RequestTimeout, + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody)) + if err != nil { + return &Result{ + Success: false, + Message: fmt.Sprintf("创建请求失败: %v", err), + } + } + + // 设置请求头 + req.Header.Set("Access-Id", AccessID) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + return &Result{ + Success: false, + Message: fmt.Sprintf("请求失败: %v", err), + } + } + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return &Result{ + Success: false, + Message: fmt.Sprintf("读取响应失败: %v", err), + } + } + + // 解析响应 + var apiResp APIResponse + if err := json.Unmarshal(respBody, &apiResp); err != nil { + return &Result{ + Success: false, + Message: fmt.Sprintf("解析响应失败: %v", err), + } + } + + fmt.Printf("API响应: %s\n", string(respBody)) + + // 处理响应 + result := &Result{ + Code: apiResp.Code, + Success: apiResp.Code == 0, + Message: apiResp.Message, + EncryptedResponse: apiResp.Data, + } + + // 如果有返回data,尝试解密 + if apiResp.Data != "" { + decryptedData, err := aesDecrypt(apiResp.Data, EncryptionKey) + if err != nil { + fmt.Printf("解密响应数据失败: %v\n", err) + result.DecryptedResponse = nil + } else { + var decryptedJSON interface{} + if err := json.Unmarshal([]byte(decryptedData), &decryptedJSON); err != nil { + fmt.Printf("解析解密后的JSON失败: %v\n", err) + result.DecryptedResponse = decryptedData + } else { + result.DecryptedResponse = decryptedJSON + } + } + } + + return result +} + +// ==================== 主程序 ==================== + +func main() { + fmt.Println("===== 个人涉诉详版 =====") + + // 调用API + result := callAPI(TestName, TestIDCard, TestMobileNo, TestAuthDate) + + fmt.Println("\n===== 结果 =====") + if result.Success { + fmt.Println("请求成功!") + if result.DecryptedResponse != nil { + decryptedJSON, _ := json.MarshalIndent(result.DecryptedResponse, "", " ") + fmt.Printf("解密后的响应: %s\n", string(decryptedJSON)) + } else { + fmt.Println("未能获取或解密响应数据") + } + } else { + fmt.Printf("请求失败: %s\n", result.Message) + } +} diff --git a/public/examples/java/demo.java b/public/examples/java/demo.java new file mode 100644 index 0000000..5aa2043 --- /dev/null +++ b/public/examples/java/demo.java @@ -0,0 +1,129 @@ +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Random; +import java.io.OutputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import org.json.JSONObject; + +public class Demo { + + // 加密 + public static String aesEncrypt(String plainText, String hexKey) throws Exception { + byte[] keyBytes = hexStringToByteArray(hexKey); + byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + + byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + + // 拼接 IV 和密文 + byte[] encryptedData = new byte[iv.length + encrypted.length]; + System.arraycopy(iv, 0, encryptedData, 0, iv.length); + System.arraycopy(encrypted, 0, encryptedData, iv.length, encrypted.length); + + return Base64.getEncoder().encodeToString(encryptedData); + } + + // 解密 + public static String aesDecrypt(String encryptedText, String hexKey) throws Exception { + byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText); + byte[] keyBytes = hexStringToByteArray(hexKey); + + byte[] iv = new byte[16]; + System.arraycopy(encryptedBytes, 0, iv, 0, 16); + + byte[] encryptedData = new byte[encryptedBytes.length - 16]; + System.arraycopy(encryptedBytes, 16, encryptedData, 0, encryptedData.length); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); + + byte[] decrypted = cipher.doFinal(encryptedData); + return new String(decrypted, StandardCharsets.UTF_8); + } + + // Helper 方法,将 16 进制字符串转为字节数组 + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + + // 发送 HTTP POST 请求 + public static String sendPostRequest(String urlString, String data, String accessId) throws Exception { + URL url = new URL(urlString); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Access-Id", accessId); + connection.setDoOutput(true); + + // 构造请求体 + String jsonInputString = "{\"data\":\"" + data + "\"}"; + try (OutputStream os = connection.getOutputStream()) { + byte[] input = jsonInputString.getBytes("utf-8"); + os.write(input, 0, input.length); + } + + // 读取响应 + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8")); + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + return response.toString(); + } + + public static void main(String[] args) { + try { + // 设置 URL、密钥和请求参数 + String url = "https://api.haiyudata.com/api/v1/IVYZ5733"; + String accessId = "XXXXXXXXXXXXX"; + String key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; + + // 请求参数 + JSONObject requestParams = new JSONObject(); + requestParams.put("name", "李四"); + requestParams.put("id_card", "110101199003076534"); + + // 将请求参数转为 JSON 字符串 + String jsonStr = requestParams.toString(); + + // 加密请求数据 + String encryptedData = aesEncrypt(jsonStr, key); + + // 发送 HTTP POST 请求并获取响应 + String response = sendPostRequest(url, encryptedData, accessId); + + // 解析响应数据 + JSONObject responseData = new JSONObject(response); + String encryptedResponseData = responseData.optString("data"); + + // 解密返回的加密数据 + if (encryptedResponseData != null) { + String decryptedResponseData = aesDecrypt(encryptedResponseData, key); + System.out.println("Decrypted Response Data: " + decryptedResponseData); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/public/examples/javascript/sms_signature_demo.js b/public/examples/javascript/sms_signature_demo.js new file mode 100644 index 0000000..a461c93 --- /dev/null +++ b/public/examples/javascript/sms_signature_demo.js @@ -0,0 +1,266 @@ +/** + * 短信发送接口签名示例(浏览器版本) + * + * 本示例演示如何在浏览器中为短信发送接口生成HMAC-SHA256签名 + * + * 安全提示: + * 1. 密钥应该通过代码混淆、字符串拆分等方式隐藏 + * 2. 不要在前端代码中直接暴露完整密钥 + * 3. 建议使用构建工具进行代码混淆和压缩 + * 4. 可以考虑将签名逻辑放在后端代理接口中 + */ + +/** + * 获取签名密钥(通过多种方式混淆,增加破解难度) + * 注意:这只是示例,实际使用时应该进一步混淆 + */ +function getSecretKey() { + // 方式1: 字符串拆分和拼接 + const part1 = 'HyApi2026' + const part2 = 'SMSSecret' + const part3 = 'Key!@#$%^' + const part4 = '&*()_+QWERTY' + const part5 = 'UIOP' + + // 方式2: 使用数组和join(增加混淆) + const arr = [part1, part2, part3, part4, part5] + return arr.join('') + + // 方式3: 字符数组拼接(更复杂的方式) + // const chars = ['T', 'y', 'A', 'p', 'i', '2', '0', '2', '4', ...]; + // return chars.join(''); + + // 方式4: 使用atob解码(如果密钥经过base64编码) + // const encoded = 'base64_encoded_string'; + // return atob(encoded); +} + +/** + * 生成随机字符串(用于nonce) + */ +function generateNonce(length = 16) { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + let result = '' + const array = new Uint8Array(length) + crypto.getRandomValues(array) + for (let i = 0; i < length; i++) { + result += chars[array[i] % chars.length] + } + return result +} + +/** + * 使用Web Crypto API生成HMAC-SHA256签名 + * + * @param {Object} params - 请求参数对象 + * @param {string} secretKey - 签名密钥 + * @param {number} timestamp - 时间戳(秒) + * @param {string} nonce - 随机字符串 + * @returns {Promise} 签名字符串(hex编码) + */ +async function generateSignature(params, secretKey, timestamp, nonce) { + // 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式 + const keys = Object.keys(params) + .filter((k) => k !== 'signature') // 排除签名字段 + .sort() + + const parts = keys.map((k) => `${k}=${params[k]}`) + + // 2. 添加时间戳和随机数 + parts.push(`timestamp=${timestamp}`) + parts.push(`nonce=${nonce}`) + + // 3. 拼接成待签名字符串 + const signString = parts.join('&') + + // 4. 使用Web Crypto API计算HMAC-SHA256签名 + const encoder = new TextEncoder() + const keyData = encoder.encode(secretKey) + const messageData = encoder.encode(signString) + + // 导入密钥 + const cryptoKey = await crypto.subtle.importKey( + 'raw', + keyData, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'], + ) + + // 计算签名 + const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData) + + // 转换为hex字符串 + const hashArray = Array.from(new Uint8Array(signature)) + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') + + return hashHex +} + +/** + * 自定义编码字符集(与后端保持一致) + */ +const CUSTOM_ENCODE_CHARSET = + '0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?' + +/** + * 自定义Base64编码(使用自定义字符集) + */ +function customBase64Encode(data) { + if (data.length === 0) return '' + + // 将字符串转换为UTF-8字节数组 + const encoder = new TextEncoder() + const bytes = encoder.encode(data) + const charset = CUSTOM_ENCODE_CHARSET + let result = '' + + // 将3个字节(24位)编码为4个字符 + for (let i = 0; i < bytes.length; i += 3) { + const b1 = bytes[i] + const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0 + const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0 + + // 组合成24位 + const combined = (b1 << 16) | (b2 << 8) | b3 + + // 分成4个6位段 + result += charset[(combined >> 18) & 0x3f] + result += charset[(combined >> 12) & 0x3f] + + if (i + 1 < bytes.length) { + result += charset[(combined >> 6) & 0x3f] + } else { + result += '=' // 填充字符 + } + + if (i + 2 < bytes.length) { + result += charset[combined & 0x3f] + } else { + result += '=' // 填充字符 + } + } + + return result +} + +/** + * 应用字符偏移混淆 + */ +function applyCharShift(data, shift) { + const charset = CUSTOM_ENCODE_CHARSET + const charsetLen = charset.length + let result = '' + + for (let i = 0; i < data.length; i++) { + const c = data[i] + if (c === '=') { + result += c // 填充字符不变 + continue + } + + const idx = charset.indexOf(c) + if (idx === -1) { + result += c // 不在字符集中,保持不变 + } else { + // 应用偏移 + const newIdx = (idx + shift) % charsetLen + result += charset[newIdx] + } + } + + return result +} + +/** + * 自定义编码请求数据 + */ +function encodeRequest(data) { + // 1. 使用自定义Base64编码 + const encoded = customBase64Encode(data) + + // 2. 应用字符偏移混淆(偏移7个位置) + const confused = applyCharShift(encoded, 7) + + return confused +} + +/** + * 发送短信验证码(带签名)- 方式2:编码后传输(推荐,更安全) + * 将所有参数(包括签名)使用自定义编码方案编码后传输,隐藏参数结构 + * + * @param {string} phone - 手机号 + * @param {string} scene - 场景(register/login/change_password/reset_password等) + * @param {string} apiBaseUrl - API基础URL + * @returns {Promise} 响应结果 + */ +async function sendSMSWithEncodedSignature(phone, scene, apiBaseUrl = 'http://localhost:8080') { + // 1. 准备参数 + const timestamp = Math.floor(Date.now() / 1000) // 当前时间戳(秒) + const nonce = generateNonce(16) // 生成随机字符串 + + const params = { + phone: phone, + scene: scene, + } + + // 2. 生成签名 + const secretKey = getSecretKey() + const signature = await generateSignature(params, secretKey, timestamp, nonce) + + // 3. 构建包含所有参数的JSON对象 + const allParams = { + phone: phone, + scene: scene, + timestamp: timestamp, + nonce: nonce, + signature: signature, + } + + // 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码 + const jsonString = JSON.stringify(allParams) + const encodedData = encodeRequest(jsonString) + + // 5. 构建请求体(只包含编码后的data字段) + const requestBody = { + data: encodedData, + } + + // 6. 发送请求 + try { + const response = await fetch(`${apiBaseUrl}/api/v1/users/send-code`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }) + + const result = await response.json() + return result + } catch (error) { + console.error('发送短信失败:', error) + throw error + } +} + +// 如果在浏览器环境中使用,可以导出到全局 +if (typeof window !== 'undefined') { + window.SMSSignature = { + sendSMSWithEncodedSignature, + generateSignature, + generateNonce, + encodeRequest, + } +} + +// 如果在Node.js环境中使用(需要安装crypto-js等库) +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + sendSMSWithEncodedSignature, + generateSignature, + generateNonce, + getSecretKey, + encodeRequest, + } +} diff --git a/public/examples/nodejs/.eslintignore b/public/examples/nodejs/.eslintignore new file mode 100644 index 0000000..57ccc75 --- /dev/null +++ b/public/examples/nodejs/.eslintignore @@ -0,0 +1,11 @@ +# ESLint 忽略配置 - 忽略示例代码目录下的所有语法错误 +# 这些是示例代码文件,不需要进行 ESLint 语法检查 + +# 忽略当前目录下的所有 .js 文件 +*.js + +# 忽略当前目录下的所有文件 +* + +# 忽略子目录 +*/ diff --git a/public/examples/nodejs/demo.js b/public/examples/nodejs/demo.js new file mode 100644 index 0000000..f1dc964 --- /dev/null +++ b/public/examples/nodejs/demo.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node + +const crypto = require('crypto') + +// ==================== API配置 ==================== +const ACCESS_ID = 'XXXXXXXXX' // 替换为您的ACCESS_ID +const APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXX' // 替换为您的app_secret +const BASE_URL = 'https://api.haiyudata.com' + +// ==================== 测试参数 ==================== +const API_CODE = 'FLXG0V4B' // 替换为您的API编号 + +const PARAMS = { + name: 'XXXX', + id_card: 'XXXXXXXXXXXXXXX', + auth_date: '20250722-20250923', +} + +// ==================== 加密解密函数 ==================== +function encrypt_data(data) { + const key = Buffer.from(APP_SECRET, 'hex') + const iv = crypto.randomBytes(16) + const cipher = crypto.createCipheriv('aes-128-cbc', key, iv) + cipher.setAutoPadding(true) + let encrypted = cipher.update(data, 'utf8') + encrypted = Buffer.concat([iv, encrypted, cipher.final()]) + return encrypted.toString('base64') +} + +function decrypt_data(encrypted_data) { + const key = Buffer.from(APP_SECRET, 'hex') + const encryptedBuffer = Buffer.from(encrypted_data, 'base64') + const iv = encryptedBuffer.slice(0, 16) + const ciphertext = encryptedBuffer.slice(16) + const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv) + decipher.setAutoPadding(true) + let decrypted = decipher.update(ciphertext) + decrypted = Buffer.concat([decrypted, decipher.final()]) + return decrypted.toString('utf8') +} + +// ==================== 主测试函数 ==================== +async function test_api() { + try { + console.log(`=== 测试API: ${API_CODE} ===`) + console.log(`请求参数: ${JSON.stringify(PARAMS, null, 2)}`) + + // 加密参数 + const params_json = JSON.stringify(PARAMS) + const encrypted_data = encrypt_data(params_json) + + // 构建请求 + const headers = { + 'Content-Type': 'application/json', + 'Access-Id': ACCESS_ID, + } + + const url = `${BASE_URL}/api/v1/${API_CODE}` + const request_data = { data: encrypted_data, options: { json: true } } + + console.log(`请求URL: ${url}`) + console.log(`请求头: ${JSON.stringify(headers, null, 2)}`) + + // 发送请求 + const start_time = Date.now() + const response = await fetch(url, { + method: 'POST', + headers: headers, + body: JSON.stringify(request_data), + signal: AbortSignal.timeout(30000), // 30秒超时 + }) + const elapsed_time = Date.now() - start_time + + console.log(`\n=== 响应信息 ===`) + console.log(`状态码: ${response.status}`) + console.log(`耗时: ${elapsed_time}ms`) + + if (response.status !== 200) { + console.log(`请求失败: ${response.status} ${response.statusText}`) + return + } + + // 解析响应 + try { + const response_json = await response.json() + console.log(`原始响应: ${JSON.stringify(response_json, null, 2)}`) + + // 检查响应格式 + if (!('code' in response_json)) { + console.log('直接返回业务数据') + return + } + + // 标准格式处理 + const api_code = response_json.code + const api_message = response_json.message || '' + const encrypted_response = response_json.data || '' + + console.log(`API响应码: ${api_code}`) + console.log(`API消息: ${api_message}`) + + if (api_code !== 0) { + console.log(`API错误: ${api_message}`) + return + } + + if (!encrypted_response) { + console.log('无加密数据返回') + return + } + + // 解密数据 + try { + const decrypted_data = decrypt_data(encrypted_response) + const result_data = JSON.parse(decrypted_data) + + console.log(`\n=== 解密后的数据 ===`) + console.log(JSON.stringify(result_data, null, 2)) + } catch (e) { + console.log(`解密失败: ${e.message}`) + } + } catch (e) { + console.log(`响应不是JSON格式: ${e.message}`) + } + } catch (e) { + console.log(`测试异常: ${e.message}`) + } +} + +// 运行测试 +test_api() diff --git a/public/examples/nodejs/sms_signature_demo.js b/public/examples/nodejs/sms_signature_demo.js new file mode 100644 index 0000000..34abfef --- /dev/null +++ b/public/examples/nodejs/sms_signature_demo.js @@ -0,0 +1,287 @@ +/** + * 短信发送接口签名示例 + * + * 本示例演示如何为短信发送接口生成HMAC-SHA256签名 + * + * 安全提示: + * 1. 密钥应该通过代码混淆、字符串拆分等方式隐藏 + * 2. 不要在前端代码中直接暴露完整密钥 + * 3. 建议使用构建工具进行代码混淆 + */ + +const crypto = require('crypto') + +/** + * 获取签名密钥(通过多种方式混淆,增加破解难度) + * 注意:这只是示例,实际使用时应该进一步混淆 + */ +function getSecretKey() { + // 方式1: 字符串拆分和拼接 + const part1 = 'HyApi2026' + const part2 = 'SMSSecret' + const part3 = 'Key!@#$%^' + const part4 = '&*()_+QWERTY' + const part5 = 'UIOP' + + // 方式2: Base64解码(可选,增加一层混淆) + // const encoded = Buffer.from('some_base64_string', 'base64').toString(); + + // 方式3: 字符数组拼接 + const chars = [ + 'T', + 'y', + 'A', + 'p', + 'i', + '2', + '0', + '2', + '4', + 'S', + 'M', + 'S', + 'S', + 'e', + 'c', + 'r', + 'e', + 't', + 'K', + 'e', + 'y', + '!', + '@', + '#', + '$', + '%', + '^', + '&', + '*', + '(', + ')', + '_', + '+', + 'Q', + 'W', + 'E', + 'R', + 'T', + 'Y', + 'U', + 'I', + 'O', + 'P', + ] + const fromChars = chars.join('') + + // 组合多种方式(实际密钥:HyApi2026SMSSecretKey!@#$%^&*()_+QWERTYUIOP) + return part1 + part2 + part3 + part4 + part5 +} + +/** + * 生成随机字符串(用于nonce) + */ +function generateNonce(length = 16) { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + let result = '' + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)) + } + return result +} + +/** + * 生成HMAC-SHA256签名 + * + * @param {Object} params - 请求参数对象 + * @param {string} secretKey - 签名密钥 + * @param {number} timestamp - 时间戳(秒) + * @param {string} nonce - 随机字符串 + * @returns {string} 签名字符串(hex编码) + */ +function generateSignature(params, secretKey, timestamp, nonce) { + // 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式 + const keys = Object.keys(params) + .filter((k) => k !== 'signature') // 排除签名字段 + .sort() + + const parts = keys.map((k) => `${k}=${params[k]}`) + + // 2. 添加时间戳和随机数 + parts.push(`timestamp=${timestamp}`) + parts.push(`nonce=${nonce}`) + + // 3. 拼接成待签名字符串 + const signString = parts.join('&') + + // 4. 使用HMAC-SHA256计算签名 + const signature = crypto.createHmac('sha256', secretKey).update(signString).digest('hex') + + return signature +} + +/** + * 自定义编码字符集(与后端保持一致) + */ +const CUSTOM_ENCODE_CHARSET = + '0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?' + +/** + * 自定义Base64编码(使用自定义字符集) + */ +function customBase64Encode(data) { + if (data.length === 0) return '' + + const bytes = Buffer.from(data, 'utf8') + const charset = CUSTOM_ENCODE_CHARSET + let result = '' + + // 将3个字节(24位)编码为4个字符 + for (let i = 0; i < bytes.length; i += 3) { + const b1 = bytes[i] + const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0 + const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0 + + // 组合成24位 + const combined = (b1 << 16) | (b2 << 8) | b3 + + // 分成4个6位段 + result += charset[(combined >> 18) & 0x3f] + result += charset[(combined >> 12) & 0x3f] + + if (i + 1 < bytes.length) { + result += charset[(combined >> 6) & 0x3f] + } else { + result += '=' // 填充字符 + } + + if (i + 2 < bytes.length) { + result += charset[combined & 0x3f] + } else { + result += '=' // 填充字符 + } + } + + return result +} + +/** + * 应用字符偏移混淆 + */ +function applyCharShift(data, shift) { + const charset = CUSTOM_ENCODE_CHARSET + const charsetLen = charset.length + let result = '' + + for (let i = 0; i < data.length; i++) { + const c = data[i] + if (c === '=') { + result += c // 填充字符不变 + continue + } + + const idx = charset.indexOf(c) + if (idx === -1) { + result += c // 不在字符集中,保持不变 + } else { + // 应用偏移 + const newIdx = (idx + shift) % charsetLen + result += charset[newIdx] + } + } + + return result +} + +/** + * 自定义编码请求数据 + */ +function encodeRequest(data) { + // 1. 使用自定义Base64编码 + const encoded = customBase64Encode(data) + + // 2. 应用字符偏移混淆(偏移7个位置) + const confused = applyCharShift(encoded, 7) + + return confused +} + +/** + * 发送短信验证码(带签名)- 方式2:编码后传输(推荐,更安全) + * 将所有参数(包括签名)使用自定义编码方案编码后传输,隐藏参数结构 + * + * @param {string} phone - 手机号 + * @param {string} scene - 场景(register/login/change_password/reset_password等) + * @param {string} apiBaseUrl - API基础URL + * @returns {Promise} 响应结果 + */ +async function sendSMSWithEncodedSignature(phone, scene, apiBaseUrl = 'http://localhost:8080') { + // 1. 准备参数 + const timestamp = Math.floor(Date.now() / 1000) // 当前时间戳(秒) + const nonce = generateNonce(16) // 生成随机字符串 + + const params = { + phone: phone, + scene: scene, + } + + // 2. 生成签名 + const secretKey = getSecretKey() + const signature = generateSignature(params, secretKey, timestamp, nonce) + + // 3. 构建包含所有参数的JSON对象 + const allParams = { + phone: phone, + scene: scene, + timestamp: timestamp, + nonce: nonce, + signature: signature, + } + + // 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码 + const jsonString = JSON.stringify(allParams) + const encodedData = encodeRequest(jsonString) + + // 5. 构建请求体(只包含编码后的data字段) + const requestBody = { + data: encodedData, + } + + // 6. 发送请求 + try { + const response = await fetch(`${apiBaseUrl}/api/v1/users/send-code`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }) + + const result = await response.json() + return result + } catch (error) { + console.error('发送短信失败:', error) + throw error + } +} + +// 使用示例 +if (require.main === module) { + console.log('=== 发送短信验证码(使用自定义编码) ===') + // 示例:发送注册验证码(使用自定义编码方案,只传递data字段) + sendSMSWithEncodedSignature('13800138000', 'register', 'http://localhost:8080') + .then((result) => { + console.log('发送成功:', result) + }) + .catch((error) => { + console.error('发送失败:', error) + }) +} + +module.exports = { + sendSMSWithEncodedSignature, + generateSignature, + generateNonce, + getSecretKey, + encodeRequest, +} diff --git a/public/examples/php/demo.php b/public/examples/php/demo.php new file mode 100644 index 0000000..44cd2d6 --- /dev/null +++ b/public/examples/php/demo.php @@ -0,0 +1,176 @@ + $mobile_no, + "id_card" => $id_card, + "auth_date" => $auth_date, + "name" => $name + ); + + // 将参数转换为JSON字符串并加密 + $json_str = json_encode($params, JSON_UNESCAPED_UNICODE); + echo "请求参数: {$json_str}\n"; + $encrypted_data = aesEncrypt($json_str, $key); + echo "加密后的数据: {$encrypted_data}\n"; + + // 发送请求 + $headers = array( + "Access-Id: {$access_id}", + "Content-Type: application/json" + ); + + $payload = array( + "data" => $encrypted_data + ); + + echo "发送请求到: {$url}\n"; + + try { + // 使用PHP原生HTTP请求方式 + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'header' => implode("\r\n", $headers), + 'content' => json_encode($payload), + 'timeout' => 30 + ) + )); + + $response = file_get_contents($url, false, $context); + + if ($response === false) { + throw new Exception("HTTP请求失败"); + } + + $response_data = json_decode($response, true); + echo "API响应: " . json_encode($response_data, JSON_UNESCAPED_UNICODE) . "\n"; + + // 处理响应 + $code = $response_data['code'] ?? null; + $message = $response_data['message'] ?? ''; + $encrypted_response_data = $response_data['data'] ?? ''; + + $result = array( + "code" => $code, + "success" => $code == 0, + "message" => $message, + "encrypted_response" => $encrypted_response_data + ); + + // 如果有返回data,尝试解密 + if ($encrypted_response_data) { + try { + $decrypted_data = aesDecrypt($encrypted_response_data, $key); + $result["decrypted_response"] = json_decode($decrypted_data, true); + } catch (Exception $e) { + echo "解密响应数据失败: {$e->getMessage()}\n"; + $result["decrypted_response"] = null; + } + } + + return $result; + + } catch (Exception $e) { + echo "请求失败: {$e->getMessage()}\n"; + return array("success" => false, "message" => "请求失败: {$e->getMessage()}"); + } +} + +/** + * 主函数 + */ +function main() { + echo "===== 个人涉诉详版 =====\n"; + + // 直接设置手机号和姓名 + $name = "XXXXXXXX"; + $id_card = "XXXXXXXXXXXXX"; + $mobile_no = "XXXXXXXXXXXXXXXXXXXX"; + $auth_date = "20250318-20270318"; + + $result = callApi($name, $id_card, $mobile_no, $auth_date); + + echo "\n===== 结果 =====\n"; + if ($result["success"]) { + echo "请求成功!\n"; + if (isset($result["decrypted_response"])) { + echo "解密后的响应: " . json_encode($result['decrypted_response'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n"; + } else { + echo "未能获取或解密响应数据\n"; + } + } else { + echo "请求失败: " . ($result['message'] ?? '未知错误') . "\n"; + } +} + +// 运行主函数 +main(); +?> diff --git a/public/examples/python/demo.py b/public/examples/python/demo.py new file mode 100644 index 0000000..06b3f61 --- /dev/null +++ b/public/examples/python/demo.py @@ -0,0 +1,124 @@ +import requests +import json +import base64 +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad, unpad +import os + +# AES CBC 加密函数,返回 Base64 +def aes_encrypt(plaintext, key): + # 将16进制密钥转换为字节 + key_bytes = bytes.fromhex(key) + # 生成随机IV + iv = os.urandom(16) + # 创建加密器 + cipher = AES.new(key_bytes, AES.MODE_CBC, iv) + # 对明文进行填充并加密 + padded_data = pad(plaintext.encode('utf-8'), AES.block_size) + encrypted_data = cipher.encrypt(padded_data) + # 连接IV和加密后的数据 + result = iv + encrypted_data + # 转换为Base64编码 + return base64.b64encode(result).decode('utf-8') + +# AES CBC 解密函数,返回解密后的明文 +def aes_decrypt(encrypted_text, key): + # 将Base64编码的加密数据转换为字节 + encrypted_bytes = base64.b64decode(encrypted_text) + # 将16进制密钥转换为字节 + key_bytes = bytes.fromhex(key) + # 从加密数据中提取IV和加密数据 + iv = encrypted_bytes[:16] + encrypted_data = encrypted_bytes[16:] + # 创建解密器 + decipher = AES.new(key_bytes, AES.MODE_CBC, iv) + # 解密并去除填充 + padded_data = decipher.decrypt(encrypted_data) + return unpad(padded_data, AES.block_size).decode('utf-8') + +def CallApi(name,id_card,mobile_no,auth_date): + # API相关配置 + interface_name = "XXXXXXXX" #接口编号 + access_id = "XXXXXXXXXXX" + key = "XXXXXXXXXXXXXXXXXXXXX" + url = f"https://api.haiyudata.com/api/v1/{interface_name}" + + # 构建请求参数 + params = { + "mobile_no": mobile_no, + "id_card": id_card, + "auth_date": auth_date, + "name": name, + } + + # 将参数转换为JSON字符串并加密 + json_str = json.dumps(params) + print(f"请求参数: {json_str}") + encrypted_data = aes_encrypt(json_str, key) + print(f"加密后的数据: {encrypted_data}") + + # 发送请求 + headers = { + "Access-Id": access_id, + "Content-Type": "application/json" + } + + payload = { + "data": encrypted_data + } + + print(f"发送请求到: {url}") + try: + response = requests.post(url, json=payload, headers=headers) + response_data = response.json() + print(f"API响应: {response_data}") + + # 处理响应 + code = response_data.get("code") + message = response_data.get("message") + encrypted_response_data = response_data.get("data") + + result = { + "code": code, + "success": code == 0, + "message": message, + "encrypted_response": encrypted_response_data + } + + # 如果有返回data,尝试解密 + if encrypted_response_data: + try: + decrypted_data = aes_decrypt(encrypted_response_data, key) + result["decrypted_response"] = json.loads(decrypted_data) + except Exception as e: + print(f"解密响应数据失败: {e}") + result["decrypted_response"] = None + + return result + except Exception as e: + print(f"请求失败: {e}") + return {"success": False, "message": f"请求失败: {e}"} + +def main(): + print("===== 个人涉诉详版 =====") + + # 直接设置手机号和姓名 + name = "XXXXXXXX" + id_card = "XXXXXXXXXXXXX" + mobile_no = "XXXXXXXXXXXXXXXXXXXX" + auth_date = "20250318-20270318" + + result = CallApi(name,id_card,mobile_no,auth_date) + + print("\n===== 结果 =====") + if result["success"]: + print("请求成功!") + if result.get("decrypted_response"): + print(f"解密后的响应: {json.dumps(result['decrypted_response'], ensure_ascii=False, indent=2)}") + else: + print("未能获取或解密响应数据") + else: + print(f"请求失败: {result.get('message', '未知错误')}") + +if __name__ == "__main__": + main() diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..da37574 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..688e054 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..8bbaab4 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/legal/yhxy.md b/public/legal/yhxy.md new file mode 100644 index 0000000..9d36067 --- /dev/null +++ b/public/legal/yhxy.md @@ -0,0 +1,61 @@ +# 海宇数据用户协议 + +## 欢迎与提示 + +欢迎访问海宇数据网站并使用我们提供的产品和服务。在完成注册程序或以任何方式使用海宇数据网站服务前,请您务必仔细阅读并透彻理解本服务条款,在确认充分理解后选择接受或不接受本服务条款。一旦您完成「同意条款并注册」或开始以其他方式使用海宇数据服务,即表明您已阅读并同意受本服务条款的约束。如您不同意本服务条款或其中任何条款约定,您应不再进行下一步或停止注册程序。 + +海宇数据再次提示您审慎阅读、充分理解各条款内容,特别是**限制或免除责任**的相应条款。限制或免除责任条款将以加粗或其他醒目形式提示您注意。如果您未满 18 周岁,请在法定监护人的陪同下阅读本服务条款。海宇数据致力于为用户提供优质的服务,同时严格遵守相关的法律法规,保护您的合法权益,确保您在使用我们的服务时能够享受到安全、便捷、高效的体验。我们始终将用户的需求和体验放在首位,通过不断优化和升级我们的服务,力求为用户提供全方位的支持和保障。我们注重用户的反馈和建议,积极进行服务的改进与创新,力求打造行业领先的数据服务平台。 + +## 一、签约主体及协议范围 + +### 1.1 协议主体 + +本服务协议是您与**海宇数科(广东横琴)科技有限公司**(以下简称「海宇数据」或「我们」)就使用海宇数据网站服务(包括提供的网页浏览、账户注册、数据查询、数据分析、API 接口调用等服务)所达成的有效合约。您通过访问和使用海宇数据的服务,表示您同意与海宇数科(广东横琴)科技有限公司订立此具有法律约束力的协议。我们致力于为用户提供高质量的数据服务,并以合规和透明的方式进行运营,确保用户的使用体验。 + +### 1.2 服务载体与具体服务 + +海宇数据网站包含域名为 [www.haiyudata.com](https://www.haiyudata.com) 的网站以及相关移动客户端(例如 APP)。如您使用或购买海宇数据网站上的某项具体服务,您可能仍需确认具体服务对应的服务条款。每项具体服务可能会包含不同的使用条款、收费标准和服务时间,建议您在使用前仔细查阅相关的协议条款。我们可能根据实际情况对服务内容进行变更,并将尽力以电子邮件、网站公告等形式通知您。若您未能在通知的期限内明确表示异议,您将被视为同意相关变更。我们将尽一切可能确保变更后的条款不影响您的基本权利和服务体验,并提供合理的过渡期以减少对用户的影响。 + +### 1.3 与单项协议的关系 + +本协议包括但不限于服务条款、隐私政策、使用说明、用户行为规范等,所有这些条款共同构成您与海宇数据之间的完整协议。若您与海宇数据之间存在其他书面协议或在线确认的条款,除非这些条款与本协议存在直接冲突,否则应共同适用于您对海宇数据服务的使用。我们建议用户在使用不同服务前,仔细阅读与其对应的具体条款,以全面了解各项服务的要求和规范,避免在服务使用中产生误解或纠纷。同时,我们承诺不断完善服务协议内容,以更好地保护用户的合法权益。 + +## 二、账户注册、使用及安全 + +### 2.1 注册资格 + +#### 2.1.1 行为能力及未成年人 + +您确认,您应具备完全民事行为能力的自然人、法人或其他组织。如您是未成年人或限制民事行为能力人,您的监护人应承担因您的不当注册行为而导致的一切后果。若您不具备前述条件,您应立即停止账户注册程序或停止使用海宇数据提供的服务。为了保障用户的合法权益,我们可能要求您提供相关的身份证明材料,以验证您的注册资格。在用户资格验证过程中,我们承诺对用户的隐私信息进行严格保护,确保不会泄露或滥用您的身份信息。 + +#### 2.1.2 出口管制与制裁 + +您还需确保自己不是任何国家、国际组织或地域实施的贸易限制、制裁或其他法律、规则限制的对象,否则可能无法正常注册和使用海宇数据服务。在此情况下,海宇数据保留取消您的注册和服务使用权限的权利,以保障公司及其他用户的合法权益不受损害。我们承诺将根据相关法律法规的要求严格保密您的个人信息,绝不将其用于与注册资格验证无关的用途。如因违反此条款导致的任何法律后果,您需自行承担全部责任。 + +### 2.2 账户注册 + +#### 2.2.1 注册成为用户 + +当您按照注册页面提示填写信息、阅读并同意本协议且完成注册程序后,您即可获得海宇数据账户并成为海宇数据的用户。我们将对您提供的注册信息予以核实,确保其真实性和有效性,并保留进一步联系以验证信息的权利。对于提供虚假信息的用户,海宇数据有权随时终止服务,并保留追究其法律责任的权利。我们建议用户提供真实、准确、完整的个人信息,以便更好地享受我们的服务。 + +#### 2.2.2 账户名称与密码 + +您在注册时设置的账户名称及密码,将用于后续登录和使用服务,请务必妥善保管。您应对您的账户信息保密,不得泄露给任何其他人,以防账户被他人恶意使用。因账户信息泄露或因您的疏忽导致账户被盗用所产生的后果由您自行承担。为了提高账户安全性,我们建议您定期更换密码,并避免使用过于简单或容易猜测的密码。同时,您可以启用双重身份验证功能以增加账户的安全性,进一步防范账户被盗风险。 + +#### 2.2.3 信息真实、准确与更新 + +您需确保账户信息的真实性和准确性,并及时更新。如您提供的信息不准确、不真实或未能及时更新,海宇数据有权采取措施限制您的使用权限,包括但不限于暂停或注销您的账户。若因您提供虚假信息导致纠纷,您应承担全部责任,同时海宇数据有权拒绝为您提供后续的服务。为了确保账户信息的真实性和安全性,我们将定期对账户信息进行核验,并可能要求您在特定情况下重新验证您的身份。对于多次提供虚假信息或拒绝验证的用户,我们将保留终止其账户服务的权利。 + +#### 2.2.4 进一步身份核验 + +海宇数据可能要求您提供更多身份信息以使用特定服务,账户在验证通过后方可获得相关使用资格。我们将根据相关法律法规的规定对您的信息进行严格保护,不会用于协议目的之外的用途。对于涉及特定敏感数据的服务,我们可能采取更严格的验证措施以确保数据安全与合规。例如,涉及金融数据或涉及高风险业务的服务,我们将要求用户提供额外的身份证明,以确保服务的合法性和安全性。我们还可能会对某些高敏感度的数据进行双重验证,以确保数据的绝对安全性和服务的合规性。对于未能通过验证的用户,我们将无法提供相关服务,敬请谅解。 + +### 2.3 账户安全 + +#### 2.3.1 保管与退出 + +您的账户和密码由您自行设置并由您保管,您应对因账户保管不善导致的任何损失负责。您需确保在每次上网时段结束时正确退出,确保账户安全,避免因公共网络或设备使用不当导致账户泄露。为保护账户安全,我们建议您不要在公共网络环境中使用海宇数据服务,并定期检查账户的登录记录。若发现任何异常登录活动,应及时更改密码并联系海宇数据的客服人员以获得帮助。 + +#### 2.3.2 通知与配合 + +若您发现账户被未经授权使用或存在其他安全问题,请立即通过海宇数据官方公布的联系方式通知我们,并配合我们采取为核实身份及保护账户安全所合理必需的措施。 diff --git a/public/legal/yszc.md b/public/legal/yszc.md new file mode 100644 index 0000000..2613529 --- /dev/null +++ b/public/legal/yszc.md @@ -0,0 +1,57 @@ +# 海宇数据隐私政策 + +**海宇数科(广东横琴)科技有限公司**(以下简称「海宇数据」或「我们」)非常重视您的隐私保护。您在使用我们的服务时,我们可能会收集和使用您的个人信息。本隐私政策旨在向您说明,我们如何收集、使用、储存和共享这些信息,以及如何为您提供访问、更正、控制和保护这些信息的方式。我们致力于维护您的个人信息的隐私、安全与保密性。请您仔细阅读本政策,确保您在充分理解的基础上使用我们的服务。使用或继续使用我们的服务,即表示您已充分理解并同意本政策的全部内容。 + +我们深知个人信息对于您的重要性,因此会采取一切合理、可行的措施来保护您的个人信息安全。我们秉承透明、合法、必要的原则进行信息的收集和处理,以最大限度地保障您的合法权益。此外,我们不断更新和改进我们的安全技术和管理措施,以应对不断变化的信息安全威胁,确保您的信息始终处于安全状态。 + +## 目录 + +1. 我们收集哪些信息及如何使用这些信息 +2. 我们如何使用 Cookie 和类似技术 +3. 我们如何共享、转让及公开披露您的个人信息 +4. 我们如何保存和保护您的个人信息 +5. 您的权利 +6. 未成年人的信息保护 +7. 个人信息的跨境传输 +8. 本政策的更新 +9. 如何联系我们 + +## 一、我们收集哪些信息及如何使用这些信息 + +### 1.1 个人信息的定义 + +「个人信息」是指以电子或其他方式记录的、能够单独或与其他信息结合识别您身份的各种信息。 +这些信息可能包括但不限于:姓名、手机号码、身份证号码、地址、位置信息、支付信息、电子邮箱、账户信息、设备信息以及其他能够识别您身份的相关数据。我们收集这些信息的目的是为了为您提供更为个性化、高效和便捷的服务。 + +### 1.2 信息的收集方式 + +#### (1)您主动提供的信息 + +在您使用我们的服务时,我们会收集您主动提供的个人信息。具体包括: + +- **注册和身份验证信息**:当您注册成为我们的用户时,您可能需要提供您的账号名称、手机号码、电子邮箱、登录密码、真实姓名、身份证号码等,以便我们为您提供更为个性化的服务。 +- **支付信息**:在使用我们的服务过程中,您可能会提供支付相关信息,如银行卡信息、交易记录、开票信息等。这些信息将用于处理支付、结算以及可能的退款。我们会确保这些信息通过加密手段进行传输和存储,以保障您的财产安全。 +- **位置信息**:当您开启设备的定位功能后,我们会通过 GPS、Wi-Fi 等技术获取您的位置信息,以便为您提供与地理位置相关的个性化服务,如附近的服务推荐、路线规划等。我们尊重您的选择,您可以通过设备设置管理是否授权我们获取您的位置信息。 +- **用户提供的内容**:您可以在我们的平台上发布评论、上传图片及其他内容,我们将收集这些您主动发布的信息,以便更好地满足您与其他用户的互动需求。我们也会分析这些信息以优化平台的用户体验。 + +#### (2)我们自动采集的信息 + +在您使用我们的服务过程中,我们会通过技术手段自动采集某些信息。具体包括: + +- **设备信息**:为了优化我们的服务体验,我们会收集设备相关的信息,包括但不限于设备型号、操作系统、设备唯一标识符(如 IMEI)、IP 地址、浏览器类型等。这些信息有助于我们更好地适配我们的服务,并确保不同设备上的用户都能够获得一致的使用体验。 +- **日志信息**:每当您访问我们的服务时,我们会自动记录访问的时间、浏览页面、点击行为、操作记录、崩溃日志等,以帮助我们更好地分析用户行为并改进服务质量。我们会将这些信息用于统计分析,以便不断改进我们的产品和服务,使其更符合用户的需求。 + +#### (3)第三方信息 + +- **第三方授权登录**:当您通过第三方账号(如微信、QQ)登录时,我们会根据您的授权从第三方获取必要的个人信息,包括昵称、头像、绑定的电话号码等,以便我们为您提供快速登录和便捷的服务体验。我们会确保获取的信息仅用于您授权的用途,并严格遵守第三方平台的相关规定。 +- **关联公司和合作伙伴提供的信息**:在获得您的明确授权后,我们可能从合作伙伴处获取相关信息,用于帮助我们更好地理解您的需求并为您提供更全面的服务。例如,我们可能从合作伙伴处获取您在其他平台的相关服务数据,以便为您提供整合的服务和用户体验。 + +#### (4)敏感个人信息 + +敏感个人信息是指一旦泄露、非法提供或滥用,可能对您的人身安全、财产安全、个人名誉、身体健康或其他方面造成损害的个人信息。常见的敏感信息包括身份证号、银行账户信息、健康数据、位置信息等。我们在收集和使用这些敏感信息时会特别谨慎,并采取严格的保护措施,确保此类信息的安全性。例如,我们在收集您的身份信息时,会使用安全的加密技术,防止未经授权的访问和使用。 + +## 二、我们如何使用 Cookie 和类似技术 + +为了能够更好地为您提供个性化的服务体验,我们和第三方合作伙伴会通过 Cookies、标签(Pixel Tags)等技术收集和存储您的信息,具体用途包括: + +- **身份验证**:帮助您在使用我们的服务时保持登录状态,以便您无需重复输入账户和密码,从而节省您的时间并提升您的使用体验。 diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..eb03f6b Binary files /dev/null and b/public/logo.png differ diff --git a/public/qrcode.jpg b/public/qrcode.jpg new file mode 100644 index 0000000..4412cff Binary files /dev/null and b/public/qrcode.jpg differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..8bb8939 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1 @@ +{"background_color":"#ffffff","display":"standalone","icons":[{"sizes":"192x192","src":"/android-chrome-192x192.png","type":"image/png"},{"sizes":"512x512","src":"/android-chrome-512x512.png","type":"image/png"}],"name":"","short_name":"","theme_color":"#ffffff"} \ No newline at end of file diff --git a/public/zh_CN.js b/public/zh_CN.js new file mode 100644 index 0000000..9198e41 --- /dev/null +++ b/public/zh_CN.js @@ -0,0 +1,93 @@ +/*! + * TinyMCE + * + * Copyright (c) 2025 Ephox Corporation DBA Tiny Technologies, Inc. + * Licensed under the Tiny commercial license. See https://www.tiny.cloud/legal/ + */ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_CN', +'

开始键盘导航

\n' + + '\n' + + '
\n' + + '
使菜单栏处于焦点
\n' + + '
Windows 或 Linux:Alt+F9
\n' + + '
macOS:⌥F9
\n' + + '
使工具栏处于焦点
\n' + + '
Windows 或 Linux:Alt+F10
\n' + + '
macOS:⌥F10
\n' + + '
使页脚处于焦点
\n' + + '
Windows 或 Linux:Alt+F11
\n' + + '
macOS:⌥F11
\n' + + '
使通知处于焦点
\n' + + '
Windows 或 Linux:Alt+F12
\n' + + '
macOS:⌥F12
\n' + + '
使上下文工具栏处于焦点
\n' + + '
Windows、Linux 或 macOS:Ctrl+F9
\n' + + '
\n' + + '\n' + + '

导航将在第一个 UI 项上开始,其中突出显示该项,或者对于页脚元素路径中的第一项,将为其添加下划线。

\n' + + '\n' + + '

在 UI 部分之间导航

\n' + + '\n' + + '

要从一个 UI 部分移至下一个,请按 Tab

\n' + + '\n' + + '

要从一个 UI 部分移至上一个,请按 Shift+Tab

\n' + + '\n' + + '

这些 UI 部分的 Tab 顺序为:

\n' + + '\n' + + '
    \n' + + '
  1. 菜单栏
  2. \n' + + '
  3. 每个工具栏组
  4. \n' + + '
  5. 边栏
  6. \n' + + '
  7. 页脚中的元素路径
  8. \n' + + '
  9. 页脚中的字数切换按钮
  10. \n' + + '
  11. 页脚中的品牌链接
  12. \n' + + '
  13. 页脚中的编辑器调整大小图柄
  14. \n' + + '
\n' + + '\n' + + '

如果不存在某个 UI 部分,则跳过它。

\n' + + '\n' + + '

如果键盘导航焦点在页脚,并且没有可见的边栏,则按 Shift+Tab 将焦点移至第一个工具栏组而非最后一个。

\n' + + '\n' + + '

在 UI 部分内导航

\n' + + '\n' + + '

要从一个 UI 元素移至下一个,请按相应的箭头键。

\n' + + '\n' + + '

箭头键

\n' + + '\n' + + '
    \n' + + '
  • 在菜单栏中的菜单之间移动。
  • \n' + + '
  • 打开菜单中的子菜单。
  • \n' + + '
  • 在工具栏组中的按钮之间移动。
  • \n' + + '
  • 在页脚的元素路径中的各项之间移动。
  • \n' + + '
\n' + + '\n' + + '

箭头键

\n' + + '\n' + + '
    \n' + + '
  • 在菜单中的菜单项之间移动。
  • \n' + + '
  • 在工具栏弹出菜单中的各项之间移动。
  • \n' + + '
\n' + + '\n' + + '

箭头键在具有焦点的 UI 部分内循环。

\n' + + '\n' + + '

要关闭打开的菜单、打开的子菜单或打开的弹出菜单,请按 Esc 键。

\n' + + '\n' + + '

如果当前的焦点在特定 UI 部分的“顶部”,则按 Esc 键还将完全退出键盘导航。

\n' + + '\n' + + '

执行菜单项或工具栏按钮

\n' + + '\n' + + '

当突出显示所需的菜单项或工具栏按钮时,按 ReturnEnter空格以执行该项。

\n' + + '\n' + + '

在非标签页式对话框中导航

\n' + + '\n' + + '

在非标签页式对话框中,当对话框打开时,第一个交互组件获得焦点。

\n' + + '\n' + + '

通过按 TabShift+Tab,在交互对话框组件之间导航。

\n' + + '\n' + + '

在标签页式对话框中导航

\n' + + '\n' + + '

在标签页式对话框中,当对话框打开时,标签页菜单中的第一个按钮获得焦点。

\n' + + '\n' + + '

通过按 TabShift+Tab,在此对话框的交互组件之间导航。

\n' + + '\n' + + '

通过将焦点移至另一对话框标签页的菜单,然后按相应的箭头键以在可用的标签页间循环,从而切换到该对话框标签页。

\n'); \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..4a209de --- /dev/null +++ b/src/App.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/api/announcement.js b/src/api/announcement.js new file mode 100644 index 0000000..91db0fb --- /dev/null +++ b/src/api/announcement.js @@ -0,0 +1,28 @@ +import request from '@/utils/request' + +// 公告管理API +export const announcementApi = { + // ==================== 用户端API ==================== + // 公告查询 + getAnnouncements: (params) => request.get('/announcements', { params }), + getAnnouncementDetail: (id) => request.get(`/announcements/${id}`), + + // ==================== 管理员端API ==================== + // 统计信息 + getAnnouncementStats: () => request.get('/admin/announcements/stats'), + + // 公告管理 + getAnnouncementsForAdmin: (params) => request.get('/admin/announcements', { params }), + createAnnouncement: (data) => request.post('/admin/announcements', data), + updateAnnouncement: (id, data) => request.put(`/admin/announcements/${id}`, data), + deleteAnnouncement: (id) => request.delete(`/admin/announcements/${id}`), + + // 公告状态管理 + publishAnnouncement: (id) => request.post(`/admin/announcements/${id}/publish`), + withdrawAnnouncement: (id) => request.post(`/admin/announcements/${id}/withdraw`), + archiveAnnouncement: (id) => request.post(`/admin/announcements/${id}/archive`), + schedulePublishAnnouncement: (id, data) => request.post(`/admin/announcements/${id}/schedule-publish`, data), + updateSchedulePublishAnnouncement: (id, data) => request.post(`/admin/announcements/${id}/update-schedule-publish`, data), + cancelSchedulePublishAnnouncement: (id) => request.post(`/admin/announcements/${id}/cancel-schedule`), +} + diff --git a/src/api/article.js b/src/api/article.js new file mode 100644 index 0000000..3e82104 --- /dev/null +++ b/src/api/article.js @@ -0,0 +1,45 @@ +import request from '@/utils/request' + +// 文章管理API +export const articleApi = { + // ==================== 用户端API ==================== + // 文章查询 + getArticles: (params) => request.get('/articles', { params }), + getArticleDetail: (id) => request.get(`/articles/${id}`), + + // 分类查询 + getCategories: () => request.get('/article-categories'), + getCategoryDetail: (id) => request.get(`/article-categories/${id}`), + + // 标签查询 + getTags: () => request.get('/article-tags'), + getTagDetail: (id) => request.get(`/article-tags/${id}`), + + // ==================== 管理员端API ==================== + // 统计信息 + getArticleStats: () => request.get('/admin/articles/stats'), + + // 文章管理 + getArticlesForAdmin: (params) => request.get('/admin/articles', { params }), + createArticle: (data) => request.post('/admin/articles', data), + updateArticle: (id, data) => request.put(`/admin/articles/${id}`, data), + deleteArticle: (id) => request.delete(`/admin/articles/${id}`), + + // 文章状态管理 + publishArticle: (id) => request.post(`/admin/articles/${id}/publish`), + schedulePublishArticle: (id, data) => request.post(`/admin/articles/${id}/schedule-publish`, data), + updateSchedulePublishArticle: (id, data) => request.post(`/admin/articles/${id}/update-schedule-publish`, data), + cancelSchedulePublishArticle: (id) => request.post(`/admin/articles/${id}/cancel-schedule`), + archiveArticle: (id) => request.post(`/admin/articles/${id}/archive`), + setFeatured: (id, data) => request.put(`/admin/articles/${id}/featured`, data), + + // 分类管理 + createCategory: (data) => request.post('/admin/article-categories', data), + updateCategory: (id, data) => request.put(`/admin/article-categories/${id}`, data), + deleteCategory: (id) => request.delete(`/admin/article-categories/${id}`), + + // 标签管理 + createTag: (data) => request.post('/admin/article-tags', data), + updateTag: (id, data) => request.put(`/admin/article-tags/${id}`, data), + deleteTag: (id) => request.delete(`/admin/article-tags/${id}`) +} diff --git a/src/api/balanceAlertApi.js b/src/api/balanceAlertApi.js new file mode 100644 index 0000000..a62bf22 --- /dev/null +++ b/src/api/balanceAlertApi.js @@ -0,0 +1,56 @@ +import request from '@/utils/request' + +/** + * 余额预警API接口 + * 注意:这些接口需要后端实现 + */ + +// 获取用户余额预警设置 +export const getUserAlertSettings = () => { + return request({ + url: '/user/balance-alert/settings', + method: 'get' + }) +} + +// 更新用户余额预警设置 +export const updateUserAlertSettings = (data) => { + return request({ + url: '/user/balance-alert/settings', + method: 'put', + data + }) +} + +// 测试预警短信发送 +export const testAlertSms = (data) => { + return request({ + url: '/user/balance-alert/test-sms', + method: 'post', + data + }) +} + +// 获取预警历史记录 +export const getAlertHistory = (params) => { + return request({ + url: '/user/balance-alert/history', + method: 'get', + params + }) +} + +// 命名导出 +export const balanceAlertApi = { + getUserAlertSettings, + updateUserAlertSettings, + testAlertSms, + getAlertHistory +} + +export default { + getUserAlertSettings, + updateUserAlertSettings, + testAlertSms, + getAlertHistory +} diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..2f4b01e --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,381 @@ +import request from '@/utils/request' +import { announcementApi } from './announcement.js' +import { articleApi } from './article.js' +import { balanceAlertApi } from './balanceAlertApi.js' +import { adminInvoiceApi, invoiceApi } from './invoice.js' + +// 直接导出发票API、文章API、公告API和余额预警API +export { adminInvoiceApi, announcementApi, articleApi, balanceAlertApi, invoiceApi } + +// 用户相关接口 - 严格按照后端路由定义 +export const userApi = { + // 发送验证码 + sendCode: (data) => request.post('/users/send-code', data), + + // 用户注册 + register: (data) => request.post('/users/register', data), + + // 密码登录 + loginWithPassword: (data) => request.post('/users/login-password', data), + + // 短信登录 + loginWithSMS: (data) => request.post('/users/login-sms', data), + + // 获取用户信息 (需认证) + getProfile: () => request.get('/users/me'), + + // 修改密码 (需认证) + changePassword: (data) => request.put('/users/me/password', data), + + // 重置密码 (无需认证) + resetPassword: (data) => request.post('/users/reset-password', data), + + // 管理员功能 - 获取用户列表 (需管理员权限) + getUserList: (params) => request.get('/users/admin/list', { params }), + + // 管理员功能 - 获取用户详情 (需管理员权限) + getUserDetail: (userId) => request.get(`/users/admin/${userId}`), + + // 管理员功能 - 获取用户统计信息 (需管理员权限) + getUserStats: () => request.get('/users/admin/stats') +} + +// 验证码(阿里云滑块)相关接口 +export const captchaApi = { + // 获取加密场景 ID,用于前端加密模式初始化滑块 + getEncryptedSceneId: (params) => request.post('/captcha/encryptedSceneId', params || {}), + // 获取验证码配置(是否启用、场景 ID) + getConfig: () => request.get('/captcha/config') +} + +// 产品相关接口 +export const productApi = { + // 产品列表(用户端接口) + getProducts: (params) => request.get('/products', { params }), + getProductDetail: (id, params) => request.get(`/products/${id}`, { params }), + searchProducts: (params) => request.get('/products/search', { params }), + + // 产品分类 + getCategories: (params) => request.get('/categories', { params }), + + // 订阅产品 + subscribeProduct: (productId) => request.post(`/products/${productId}/subscribe`), + + // 产品API配置 + getProductApiConfig: (productId) => request.get(`/products/${productId}/api-config`), + getProductApiConfigByCode: (productCode) => request.get(`/products/code/${productCode}/api-config`), + getProductApiConfigsByProductIDs: (productIds) => request.get('/products/api-configs', { + params: { product_ids: productIds.join(',') } + }), + + // 下载接口文档(支持PDF和Markdown) + downloadProductDocumentation: (productId) => request.get(`/products/${productId}/documentation/download`, { + responseType: 'blob' + }), + + // 组件报告下载相关API + // 检查产品是否可以下载示例报告 + checkComponentReportAvailability: (productId) => request.get(`/products/${productId}/component-report/check`), + // 获取产品示例报告下载信息和价格计算 + getComponentReportInfo: (productId) => request.get(`/products/${productId}/component-report/info`), + // 创建示例报告购买订单 + createComponentReportPaymentOrder: (productId, data) => request.post(`/products/${productId}/component-report/create-order`, data), + // 检查示例报告购买订单支付状态 + checkComponentReportPaymentStatus: (orderId) => request.get(`/component-report/check-payment/${orderId}`), + // 生成并下载示例报告ZIP文件 + generateAndDownloadComponentReport: (data) => request.post('/component-report/generate-and-download', data, { responseType: 'blob' }) +} + +// 分类相关接口 - 数据大厅 +export const categoryApi = { + // 获取分类列表 (公开接口) + getCategories: (params) => request.get('/categories', { params }), + + // 获取分类详情 (公开接口) + getCategoryDetail: (id) => request.get(`/categories/${id}`) +} + +// 订阅相关接口 - 我的订阅 +export const subscriptionApi = { + // 获取我的订阅列表 (需认证) + getMySubscriptions: (params) => request.get('/my/subscriptions', { params }), + + // 获取我的订阅统计 (需认证) + getMySubscriptionStats: () => request.get('/my/subscriptions/stats'), + + // 获取我的订阅详情 (需认证) + getMySubscriptionDetail: (id) => request.get(`/my/subscriptions/${id}`), + + // 获取我的订阅使用情况 (需认证) + getMySubscriptionUsage: (id) => request.get(`/my/subscriptions/${id}/usage`), + + // 取消我的订阅 (需认证) + cancelMySubscription: (id) => request.post(`/my/subscriptions/${id}/cancel`) +} + +// 财务相关接口 +export const financeApi = { + // 钱包相关 + createWallet: (data) => request.post('/finance/wallet', data), + getWallet: () => request.get('/finance/wallet'), + updateWallet: (data) => request.put('/finance/wallet', data), + rechargeWallet: (data) => request.post('/finance/wallet/recharge', data), + withdrawWallet: (data) => request.post('/finance/wallet/withdraw', data), + walletTransaction: (data) => request.post('/finance/wallet/transaction', data), + getWalletStats: () => request.get('/finance/wallet/stats'), + getRechargeConfig: () => request.get('/finance/wallet/recharge-config'), + + // 充值相关 + transferRecharge: (data) => request.post('/finance/wallet/transfer-recharge', data), + giftRecharge: (data) => request.post('/finance/wallet/gift-recharge', data), + createAlipayRecharge: (data) => request.post('/finance/wallet/alipay-recharge', data), + createWechatRecharge: (data) => request.post('/finance/wallet/wechat-recharge', data), + getWechatOrderStatus: (params) => request.get('/finance/wallet/wechat-order-status', { params }), + + // 用户密钥相关 + createUserSecrets: (data) => request.post('/finance/secrets', data), + getUserSecrets: () => request.get('/finance/secrets'), + regenerateAccessKey: () => request.post('/finance/secrets/regenerate'), + deactivateUserSecrets: () => request.post('/finance/secrets/deactivate'), + + // 钱包交易记录 + getUserWalletTransactions: (params) => request.get('/finance/wallet/transactions', { params }), + + // 支付宝订单状态查询 + getAlipayOrderStatus: (params) => request.get('/finance/wallet/alipay-order-status', { params }), + + // 管理员充值功能 + transferRecharge: (data) => request.post('/admin/finance/transfer-recharge', data), + giftRecharge: (data) => request.post('/admin/finance/gift-recharge', data), + + // 充值记录相关接口 + getUserRechargeRecords: (params) => request.get('/finance/wallet/recharge-records', { params }), + getAdminRechargeRecords: (params) => request.get('/admin/finance/recharge-records', { params }), + + // 购买记录相关接口 + getUserPurchaseRecords: (params) => request.get('/finance/purchase-records', { params }), + getAdminPurchaseRecords: (params) => request.get('/admin/finance/purchase-records', { params }), + exportAdminPurchaseRecords: (params) => request.get('/admin/finance/purchase-records/export', { + params, + responseType: 'blob' + }) +} + +// 认证相关接口 +export const certificationApi = { + // 获取认证详情 + getCertificationDetails: () => request.get('/certifications/details'), + + // 获取认证进度 + getCertificationProgress: () => request.get('/certifications/progress'), + + // 提交企业信息 + submitEnterpriseInfo: (data) => request.post('/certifications/enterprise-info', data), + + // 发起人脸识别验证 + initiateFaceVerify: (data) => request.post('/certifications/face-verify', data), + + // 申请合同签署 + applyContract: () => request.post('/certifications/apply-contract'), + + // 获取认证详情 + getCertificationDetails: () => request.get('/certifications/details'), + + // 确认签署状态 + confirmSign: (data) => request.post('/certifications/confirm-sign', data), + + // 确认认证状态 + confirmAuth: (data) => request.post('/certifications/confirm-auth', data), + + // OCR营业执照识别 + recognizeBusinessLicense: (formData) => request.post('/certifications/ocr/business-license', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }), + + // 上传认证图片到七牛云(企业信息中的营业执照、办公场地、场景附件、授权代表身份证等) + uploadFile: (file) => { + const formData = new FormData() + formData.append('file', file) + return request.post('/certifications/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + // 管理员代用户完成认证(暂不关联合同) + adminCompleteWithoutContract: (data) => request.post('/certifications/admin/complete-without-contract', data), + + // 管理端企业审核:列表(按状态机 certification_status 筛选)、详情、通过、拒绝、按用户变更状态 + adminListSubmitRecords: (params) => request.get('/certifications/admin/submit-records', { params }), + adminGetSubmitRecord: (id) => request.get(`/certifications/admin/submit-records/${id}`), + adminApproveSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/approve`, data || {}), + adminRejectSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/reject`, data), + // 管理端按用户变更认证状态(以状态机为准:info_submitted=通过 / info_rejected=拒绝) + adminTransitionCertificationStatus: (data) => request.post('/certifications/admin/transition-status', data) +} + +// API相关接口 +export const apiApi = { + // 用户API调用记录 + getUserApiCalls: (params) => request.get('/my/api-calls', { params }), + + // 加密参数接口(用于前端调试) + encryptParams: (data, secretKey) => request.post('/encrypt', { data, secret_key: secretKey }), + + // 解密参数接口(用于前端调试) + decryptParams: (encryptedData, secretKey) => request.post('/decrypt', { encrypted_data: encryptedData, secret_key: secretKey }) +} + +// API调用记录API +export const apiCallApi = { + // 用户端API调用记录 + getUserApiCalls: (params) => request.get('/my/api-calls', { params }), + + // 管理端API调用记录 + getAdminApiCalls: (params) => request.get('/admin/api-calls', { params }), + + // 管理端导出API调用记录 + exportAdminApiCalls: (params) => request.get('/admin/api-calls/export', { + params, + responseType: 'blob' + }) +} + +// 钱包交易记录API +export const walletTransactionApi = { + // 用户端消费记录 + getUserWalletTransactions: (params) => request.get('/finance/wallet/transactions', { params }), + + // 管理端消费记录 + getAdminWalletTransactions: (params) => request.get('/admin/wallet-transactions', { params }), + + // 管理端导出消费记录 + exportAdminWalletTransactions: (params) => request.get('/admin/wallet-transactions/export', { + params, + responseType: 'blob' + }) +} + +// 充值记录API +export const rechargeRecordApi = { + // 用户端充值记录 + getUserRechargeRecords: (params) => request.get('/finance/wallet/recharge-records', { params }), + + // 管理端充值记录 + getAdminRechargeRecords: (params) => request.get('/admin/recharge-records', { params }), + + // 管理端导出充值记录 + exportAdminRechargeRecords: (params) => request.get('/admin/recharge-records/export', { + params, + responseType: 'blob' + }) +} + +// API密钥相关接口 +export const apiKeysApi = { + // 获取用户API密钥 (需认证) + getUserApiKeys: () => request.get('/api-keys') +} + +export const whiteListApi = { + // 获取用户白名单列表 (需认证) + getWhiteList: (remark = '') => { + const params = {} + if (remark) { + params.remark = remark + } + return request.get('/white-list', { params }) + }, + // 添加白名单IP (需认证) + addWhiteListIP: (ipAddress, remark = '') => request.post('/white-list', { ip_address: ipAddress, remark: remark }), + // 删除白名单IP (需认证) + deleteWhiteListIP: (ipAddress) => request.delete(`/white-list/${encodeURIComponent(ipAddress)}`) +} + +// 管理员接口 - 需要管理员权限 +export const productAdminApi = { + // 产品管理 + getProducts: (params) => request.get('/admin/products', { params }), + getProductDetail: (id) => request.get(`/admin/products/${id}`), + createProduct: (data) => request.post('/admin/products', data), + updateProduct: (id, data) => request.put(`/admin/products/${id}`, data), + deleteProduct: (id) => request.delete(`/admin/products/${id}`), + + // 组合包管理 + getAvailableProducts: (params) => request.get('/admin/products/available', { params }), + addPackageItem: (packageId, data) => request.post(`/admin/products/${packageId}/package-items`, data), + updatePackageItem: (packageId, itemId, data) => request.put(`/admin/products/${packageId}/package-items/${itemId}`, data), + removePackageItem: (packageId, itemId) => request.delete(`/admin/products/${packageId}/package-items/${itemId}`), + reorderPackageItems: (packageId, data) => request.put(`/admin/products/${packageId}/package-items/reorder`, data), + updatePackageItems: (packageId, data) => request.put(`/admin/products/${packageId}/package-items/batch`, data), + + // 分类管理 + getCategories: (params) => request.get('/admin/product-categories', { params }), + getCategoryDetail: (id) => request.get(`/admin/product-categories/${id}`), + createCategory: (data) => request.post('/admin/product-categories', data), + updateCategory: (id, data) => request.put(`/admin/product-categories/${id}`, data), + deleteCategory: (id) => request.delete(`/admin/product-categories/${id}`), + + // 小类管理 + getSubCategories: (params) => request.get('/admin/sub-categories', { params }), + getSubCategoryDetail: (id) => request.get(`/admin/sub-categories/${id}`), + createSubCategory: (data) => request.post('/admin/sub-categories', data), + updateSubCategory: (id, data) => request.put(`/admin/sub-categories/${id}`, data), + deleteSubCategory: (id) => request.delete(`/admin/sub-categories/${id}`), + getSubCategoriesByCategory: (categoryId) => request.get(`/admin/product-categories/${categoryId}/sub-categories`), + + // 订阅管理 + getSubscriptions: (params) => request.get('/admin/subscriptions', { params }), + getSubscriptionStats: () => request.get('/admin/subscriptions/stats'), + updateSubscriptionPrice: (id, data) => request.put(`/admin/subscriptions/${id}/price`, data), + batchUpdateSubscriptionPrices: (data) => request.post('/admin/subscriptions/batch-update-prices', data), + + // 产品API配置管理 + getProductApiConfig: (productId) => request.get(`/admin/products/${productId}/api-config`), + createProductApiConfig: (productId, data) => request.post(`/admin/products/${productId}/api-config`, data), + updateProductApiConfig: (productId, data) => request.put(`/admin/products/${productId}/api-config`, data), + deleteProductApiConfig: (productId) => request.delete(`/admin/products/${productId}/api-config`), + + // 产品文档管理 + getProductDocumentation: (productId) => request.get(`/admin/products/${productId}/documentation`), + createProductDocumentation: (productId, data) => request.post(`/admin/products/${productId}/documentation`, data), + updateProductDocumentation: (productId, data) => request.put(`/admin/products/${productId}/documentation`, data), + deleteProductDocumentation: (productId) => request.delete(`/admin/products/${productId}/documentation`) +} + +// 表单配置相关接口 +export const formConfigApi = { + // 获取指定API的表单配置 + getFormConfig: (apiCode) => request.get(`/form-config/${apiCode}`) +} + +// Console专用API调用接口 +export const consoleApi = { + // 调用产品API(使用JWT认证,不需要域名认证) + callProductAPI: (apiCode, requestBody, accessId) => { + return request.post(`/console/${apiCode}`, requestBody, { + headers: { + 'Access-Id': accessId + } + }) + } +} + +// 导出所有API +export default { + user: userApi, + certification: certificationApi, + finance: financeApi, + product: productApi, + category: categoryApi, + subscription: subscriptionApi, + productAdmin: productAdminApi, + apiKeys: apiKeysApi, + whiteList: whiteListApi, + api: apiApi, + invoice: invoiceApi, + adminInvoice: adminInvoiceApi +} diff --git a/src/api/invoice.js b/src/api/invoice.js new file mode 100644 index 0000000..deb5c1b --- /dev/null +++ b/src/api/invoice.js @@ -0,0 +1,98 @@ +import request from '@/utils/request' + +// 发票API接口 +export const invoiceApi = { + // 申请开票 + applyInvoice(data) { + return request({ + url: '/invoices/apply', + method: 'post', + data + }) + }, + + // 获取用户发票信息 + getUserInvoiceInfo() { + return request({ + url: '/invoices/info', + method: 'get' + }) + }, + + // 更新用户发票信息 + updateUserInvoiceInfo(data) { + return request({ + url: '/invoices/info', + method: 'put', + data + }) + }, + + // 获取用户开票记录 + getUserInvoiceRecords(params) { + return request({ + url: '/invoices/records', + method: 'get', + params + }) + }, + + // 获取可开票金额 + getAvailableAmount() { + return request({ + url: '/invoices/available-amount', + method: 'get' + }) + }, + + // 下载发票文件 + downloadInvoiceFile(applicationId) { + return request({ + url: `/invoices/${applicationId}/download`, + method: 'get', + responseType: 'blob' + }) + } +} + +// 管理员发票API接口 +export const adminInvoiceApi = { + // 获取待处理的发票申请列表 + getPendingApplications(params) { + return request({ + url: '/admin/invoices/pending', + method: 'get', + params + }) + }, + + // 通过发票申请(上传发票) + approveInvoiceApplication(applicationId, formData) { + return request({ + url: `/admin/invoices/${applicationId}/approve`, + method: 'post', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + // 拒绝发票申请 + rejectInvoiceApplication(applicationId, data) { + return request({ + url: `/admin/invoices/${applicationId}/reject`, + method: 'post', + data + }) + }, + + // 下载发票文件(管理员) + downloadInvoiceFile(applicationId) { + return request({ + url: `/admin/invoices/${applicationId}/download`, + method: 'get', + responseType: 'blob' + }) + } +} diff --git a/src/api/statistics/index.js b/src/api/statistics/index.js new file mode 100644 index 0000000..9ec2ce1 --- /dev/null +++ b/src/api/statistics/index.js @@ -0,0 +1,689 @@ +import request from '@/utils/request' + +// ================ 用户端统计接口 ================ + +/** + * 获取公开统计信息 + * @returns {Promise} + */ +export function getPublicStatistics() { + return request({ + url: '/statistics/public', + method: 'get' + }) +} + +/** + * 获取用户统计信息 + * @returns {Promise} + */ +export function getUserStatistics() { + return request({ + url: '/statistics/user', + method: 'get' + }) +} + +// ================ 独立统计接口 ================ + +/** + * 获取API调用统计 + * @param {Object} params - 查询参数 {user_id?, start_date, end_date, unit} + * @returns {Promise} + */ +export function getApiCallsStatistics(params) { + return request({ + url: '/statistics/api-calls', + method: 'get', + params + }) +} + +/** + * 获取消费统计 + * @param {Object} params - 查询参数 {user_id?, start_date, end_date, unit} + * @returns {Promise} + */ +export function getConsumptionStatistics(params) { + return request({ + url: '/statistics/consumption', + method: 'get', + params + }) +} + +/** + * 获取充值统计 + * @param {Object} params - 查询参数 {user_id?, start_date, end_date, unit} + * @returns {Promise} + */ +export function getRechargeStatistics(params) { + return request({ + url: '/statistics/recharge', + method: 'get', + params + }) +} + +/** + * 获取最新产品推荐 + * @param {Object} params - 查询参数 {limit?} + * @returns {Promise} + */ +export function getLatestProducts(params = {}) { + return request({ + url: '/statistics/latest-products', + method: 'get', + params + }) +} + + +/** + * 获取指标列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getMetrics(params) { + return request({ + url: '/statistics/metrics', + method: 'get', + params + }) +} + +/** + * 获取单个指标 + * @param {string} id - 指标ID + * @returns {Promise} + */ +export function getMetric(id) { + return request({ + url: `/statistics/metrics/${id}`, + method: 'get' + }) +} + +/** + * 获取报告列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getReports(params) { + return request({ + url: '/statistics/reports', + method: 'get', + params + }) +} + +/** + * 获取单个报告 + * @param {string} id - 报告ID + * @returns {Promise} + */ +export function getReport(id) { + return request({ + url: `/statistics/reports/${id}`, + method: 'get' + }) +} + +/** + * 获取仪表板列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getDashboards(params) { + return request({ + url: '/statistics/dashboards', + method: 'get', + params + }) +} + +/** + * 获取单个仪表板 + * @param {string} id - 仪表板ID + * @returns {Promise} + */ +export function getDashboard(id) { + return request({ + url: `/statistics/dashboards/${id}`, + method: 'get' + }) +} + +/** + * 获取仪表板数据 + * @param {string} id - 仪表板ID + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getDashboardData(id, params) { + return request({ + url: `/statistics/dashboards/${id}/data`, + method: 'get', + params + }) +} + +// ================ 管理员统计接口 ================ + +/** + * 管理员获取系统统计 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function adminGetSystemStatistics(params) { + return request({ + url: '/admin/statistics/system', + method: 'get', + params + }) +} + +/** + * 管理员获取单个用户统计 + * @param {string} userId - 用户ID + * @returns {Promise} + */ +export function adminGetUserStatistics(userId) { + return request({ + url: `/admin/statistics/users/${userId}`, + method: 'get' + }) +} + +/** + * 管理员获取API调用统计 + * @param {Object} params - 查询参数 {start_date, end_date, unit} + * @returns {Promise} + */ +export function adminGetApiCallsStatistics(params) { + return request({ + url: '/admin/statistics/api-calls', + method: 'get', + params + }) +} + +/** + * 管理员获取消费统计 + * @param {Object} params - 查询参数 {start_date, end_date, unit} + * @returns {Promise} + */ +export function adminGetConsumptionStatistics(params) { + return request({ + url: '/admin/statistics/consumption', + method: 'get', + params + }) +} + +/** + * 管理员获取充值统计 + * @param {Object} params - 查询参数 {start_date, end_date, unit} + * @returns {Promise} + */ +export function adminGetRechargeStatistics(params) { + return request({ + url: '/admin/statistics/recharge', + method: 'get', + params + }) +} + + +// ================ 管理员独立域统计接口 ================ + +/** + * 管理员获取用户域统计 + * @param {Object} params - 查询参数 {period, start_date, end_date} + * @returns {Promise} + */ +export function adminGetUserDomainStatistics(params) { + return request({ + url: '/admin/statistics/user-domain', + method: 'get', + params + }) +} + +/** + * 管理员获取API域统计 + * @param {Object} params - 查询参数 {period, start_date, end_date} + * @returns {Promise} + */ +export function adminGetApiDomainStatistics(params) { + return request({ + url: '/admin/statistics/api-domain', + method: 'get', + params + }) +} + +/** + * 管理员获取消费域统计 + * @param {Object} params - 查询参数 {period, start_date, end_date} + * @returns {Promise} + */ +export function adminGetConsumptionDomainStatistics(params) { + return request({ + url: '/admin/statistics/consumption-domain', + method: 'get', + params + }) +} + +/** + * 管理员获取充值域统计 + * @param {Object} params - 查询参数 {period, start_date, end_date} + * @returns {Promise} + */ +export function adminGetRechargeDomainStatistics(params) { + return request({ + url: '/admin/statistics/recharge-domain', + method: 'get', + params + }) +} + +/** + * 管理员触发数据聚合 + * @param {Object} data - 聚合参数 + * @returns {Promise} + */ +export function adminTriggerAggregation(data) { + return request({ + url: '/admin/statistics/aggregation/trigger', + method: 'post', + data + }) +} + +/** + * 管理员创建指标 + * @param {Object} data - 指标数据 + * @returns {Promise} + */ +export function adminCreateMetric(data) { + return request({ + url: '/admin/statistics/metrics', + method: 'post', + data + }) +} + +/** + * 管理员更新指标 + * @param {string} id - 指标ID + * @param {Object} data - 指标数据 + * @returns {Promise} + */ +export function adminUpdateMetric(id, data) { + return request({ + url: `/admin/statistics/metrics/${id}`, + method: 'put', + data + }) +} + +/** + * 管理员删除指标 + * @param {string} id - 指标ID + * @returns {Promise} + */ +export function adminDeleteMetric(id) { + return request({ + url: `/admin/statistics/metrics/${id}`, + method: 'delete' + }) +} + +/** + * 管理员获取指标列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function adminGetMetrics(params) { + return request({ + url: '/admin/statistics/metrics', + method: 'get', + params + }) +} + +/** + * 管理员获取单个指标 + * @param {string} id - 指标ID + * @returns {Promise} + */ +export function adminGetMetric(id) { + return request({ + url: `/admin/statistics/metrics/${id}`, + method: 'get' + }) +} + +/** + * 管理员获取报告列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function adminGetReports(params) { + return request({ + url: '/admin/statistics/reports', + method: 'get', + params + }) +} + +/** + * 管理员获取单个报告 + * @param {string} id - 报告ID + * @returns {Promise} + */ +export function adminGetReport(id) { + return request({ + url: `/admin/statistics/reports/${id}`, + method: 'get' + }) +} + +/** + * 管理员删除报告 + * @param {string} id - 报告ID + * @returns {Promise} + */ +export function adminDeleteReport(id) { + return request({ + url: `/admin/statistics/reports/${id}`, + method: 'delete' + }) +} + +/** + * 管理员创建仪表板 + * @param {Object} data - 仪表板数据 + * @returns {Promise} + */ +export function adminCreateDashboard(data) { + return request({ + url: '/admin/statistics/dashboards', + method: 'post', + data + }) +} + +/** + * 管理员更新仪表板 + * @param {string} id - 仪表板ID + * @param {Object} data - 仪表板数据 + * @returns {Promise} + */ +export function adminUpdateDashboard(id, data) { + return request({ + url: `/admin/statistics/dashboards/${id}`, + method: 'put', + data + }) +} + +/** + * 管理员删除仪表板 + * @param {string} id - 仪表板ID + * @returns {Promise} + */ +export function adminDeleteDashboard(id) { + return request({ + url: `/admin/statistics/dashboards/${id}`, + method: 'delete' + }) +} + +/** + * 管理员获取仪表板列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function adminGetDashboards(params) { + return request({ + url: '/admin/statistics/dashboards', + method: 'get', + params + }) +} + +/** + * 管理员获取单个仪表板 + * @param {string} id - 仪表板ID + * @returns {Promise} + */ +export function adminGetDashboard(id) { + return request({ + url: `/admin/statistics/dashboards/${id}`, + method: 'get' + }) +} + +/** + * 管理员获取仪表板数据 + * @param {string} id - 仪表板ID + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function adminGetDashboardData(id, params) { + return request({ + url: `/admin/statistics/dashboards/${id}/data`, + method: 'get', + params + }) +} + +// ================ 便捷方法 ================ + +/** + * 获取API调用统计 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getApiCallStats(params = {}) { + return getMetrics({ + metric_type: 'api_calls', + ...params + }) +} + +/** + * 获取用户统计 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getUserStats(params = {}) { + return getMetrics({ + metric_type: 'users', + ...params + }) +} + +/** + * 获取财务统计 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getFinanceStats(params = {}) { + return getMetrics({ + metric_type: 'finance', + ...params + }) +} + +/** + * 获取产品统计 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getProductStats(params = {}) { + return getMetrics({ + metric_type: 'products', + ...params + }) +} + +/** + * 获取认证统计 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getCertificationStats(params = {}) { + return getMetrics({ + metric_type: 'certification', + ...params + }) +} + +/** + * 获取今日仪表板数据 + * @param {string} dashboardId - 仪表板ID + * @param {string} userRole - 用户角色 + * @returns {Promise} + */ +export function getTodayDashboardData(dashboardId, userRole = 'user') { + return getDashboardData(dashboardId, { + user_role: userRole, + period: 'today' + }) +} + +/** + * 获取本周仪表板数据 + * @param {string} dashboardId - 仪表板ID + * @param {string} userRole - 用户角色 + * @returns {Promise} + */ +export function getWeekDashboardData(dashboardId, userRole = 'user') { + return getDashboardData(dashboardId, { + user_role: userRole, + period: 'week' + }) +} + +/** + * 获取本月仪表板数据 + * @param {string} dashboardId - 仪表板ID + * @param {string} userRole - 用户角色 + * @returns {Promise} + */ +export function getMonthDashboardData(dashboardId, userRole = 'user') { + return getDashboardData(dashboardId, { + user_role: userRole, + period: 'month' + }) +} + +/** + * 获取自定义时间范围仪表板数据 + * @param {string} dashboardId - 仪表板ID + * @param {string} userRole - 用户角色 + * @param {string} startDate - 开始日期 + * @param {string} endDate - 结束日期 + * @returns {Promise} + */ +export function getCustomDashboardData(dashboardId, userRole, startDate, endDate) { + return getDashboardData(dashboardId, { + user_role: userRole, + start_date: startDate, + end_date: endDate + }) +} + +// ================ 管理员排行榜接口 ================ + +/** + * 获取用户调用排行榜 + * @param {Object} params - 查询参数 + * @param {string} params.type - 排行类型 (calls, consumption) + * @param {string} params.period - 时间周期 (today, month, total) + * @param {number} params.limit - 返回数量 + * @returns {Promise} + */ +export function adminGetUserCallRanking(params = {}) { + return request({ + url: '/admin/statistics/user-call-ranking', + method: 'get', + params + }) +} + +/** + * 获取充值排行榜 + * @param {Object} params - 查询参数 + * @param {string} params.period - 时间周期 (today, month, total) + * @param {number} params.limit - 返回数量 + * @returns {Promise} + */ +export function adminGetRechargeRanking(params = {}) { + return request({ + url: '/admin/statistics/recharge-ranking', + method: 'get', + params + }) +} + +/** + * 获取API受欢迎程度排行榜 + * @param {Object} params - 查询参数 + * @param {string} params.period - 时间周期 (today, month, total) + * @param {number} params.limit - 返回数量 + * @returns {Promise} + */ +export function adminGetApiPopularityRanking(params = {}) { + return request({ + url: '/admin/statistics/api-popularity-ranking', + method: 'get', + params + }) +} + +/** + * 获取今日认证企业列表 + * @param {Object} params - 查询参数 + * @param {number} params.limit - 返回数量 + * @returns {Promise} + */ +export function adminGetTodayCertifiedEnterprises(params = {}) { + return request({ + url: '/admin/statistics/today-certified-enterprises', + method: 'get', + params + }) +} + +// ================ 管理员安全可视化接口 ================ + +/** + * 获取可疑IP列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function adminGetSuspiciousIPList(params = {}) { + return request({ + url: '/admin/security/suspicious-ip/list', + method: 'get', + params + }) +} + +/** + * 获取可疑IP地球请求流 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function adminGetSuspiciousIPGeoStream(params = {}) { + return request({ + url: '/admin/security/suspicious-ip/geo-stream', + method: 'get', + params + }) +} diff --git a/src/api/ui-component.js b/src/api/ui-component.js new file mode 100644 index 0000000..89fb0ab --- /dev/null +++ b/src/api/ui-component.js @@ -0,0 +1,99 @@ +import request from '@/utils/request' + +export const uiComponentApi = { + // 获取UI组件列表 + getUIComponentList(params) { + return request.get('/admin/ui-components', { params }) + }, + + // 获取UI组件详情 + getUIComponentDetail(id) { + return request.get(`/admin/ui-components/${id}`) + }, + + // 创建UI组件 + createUIComponent(data) { + // 确保发送的数据结构与后端期望的完全匹配 + const requestData = { + component_code: data.component_code || '', + component_name: data.component_name || '', + description: data.description || '', + version: data.version || '', + is_active: data.is_active !== undefined ? data.is_active : true, + sort_order: data.sort_order !== undefined ? data.sort_order : 0 + } + + // 添加调试日志 + console.log('创建UI组件请求数据:', requestData) + + return request.post('/admin/ui-components', requestData) + }, + + // 更新UI组件 + updateUIComponent(id, data) { + // 确保发送的数据结构与后端期望的完全匹配 + const requestData = { + id: id, + component_code: data.component_code || '', + component_name: data.component_name || '', + description: data.description || '', + version: data.version || '', + is_active: data.is_active !== undefined ? data.is_active : true, + sort_order: data.sort_order !== undefined ? data.sort_order : 0 + } + + // 添加调试日志 + console.log('更新UI组件请求数据:', requestData) + + return request.put(`/admin/ui-components/${id}`, requestData) + }, + + // 删除UI组件 + deleteUIComponent(id) { + return request.delete(`/admin/ui-components/${id}`) + }, + + // 上传UI组件文件 + uploadUIComponentFile(id, formData) { + return request.post(`/admin/ui-components/${id}/upload`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + // 上传并解压UI组件文件 + uploadAndExtractUIComponentFile(id, formData) { + return request.post(`/admin/ui-components/${id}/upload-extract`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + // 下载UI组件文件 + downloadUIComponentFile(id) { + return request.get(`/admin/ui-components/${id}/download`, { + responseType: 'blob' + }) + }, + + // 获取UI组件文件夹内容 + getUIComponentFolderContent(id) { + return request.get(`/admin/ui-components/${id}/folder-content`) + }, + + // 删除UI组件文件夹 + deleteUIComponentFolder(id) { + return request.delete(`/admin/ui-components/${id}/folder`) + }, + + // 创建UI组件并上传文件(合并操作) + createUIComponentWithFile(formData) { + return request.post('/admin/ui-components/create-with-file', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + } +} diff --git a/src/assets/base.css b/src/assets/base.css new file mode 100644 index 0000000..f3e8147 --- /dev/null +++ b/src/assets/base.css @@ -0,0 +1,38 @@ + + +*, +*::before, +*::after { + box-sizing: border-box; + font-weight: normal; +} +:root { + /* --color-background: #f5f7fa; */ + --color-text: #222; +} +body { + min-height: 100vh; + color: var(--color-text); + /* background: var(--color-background); */ + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..eb03f6b Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/src/assets/main.css b/src/assets/main.css new file mode 100644 index 0000000..280def5 --- /dev/null +++ b/src/assets/main.css @@ -0,0 +1,3 @@ +@import './base.css'; +@import "tailwindcss"; +@plugin "@tailwindcss/typography"; diff --git a/src/assets/styles/auth.css b/src/assets/styles/auth.css new file mode 100644 index 0000000..63dd2ce --- /dev/null +++ b/src/assets/styles/auth.css @@ -0,0 +1,120 @@ +/* 认证页面全局样式 */ + +/* 输入框样式优化 */ +.auth-input :deep(.el-input__wrapper) { + border-radius: 8px !important; + transition: all 0.3s ease !important; + border: 1px solid #d1d5db !important; +} + +.auth-input :deep(.el-input__wrapper:hover) { + border-color: #3b82f6 !important; +} + +.auth-input :deep(.el-input__wrapper.is-focus) { + border-color: #3b82f6 !important; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1) !important; +} + +/* 按钮样式优化 */ +.auth-button :deep(.el-button--primary) { + border-radius: 8px !important; + font-weight: 500 !important; + transition: all 0.3s ease !important; + background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%) !important; + border: none !important; +} + +.auth-button :deep(.el-button--primary:hover) { + transform: translateY(-1px) !important; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important; + background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%) !important; +} + +.auth-button :deep(.el-button--primary:active) { + transform: translateY(0) !important; +} + +/* Radio button 样式优化 */ +.auth-radio :deep(.el-radio-button__inner) { + border: none !important; + background: transparent !important; + color: #6b7280 !important; + font-weight: 500 !important; + transition: all 0.3s ease !important; + padding: 12px 16px !important; +} + +.auth-radio :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { + background: white !important; + color: #3b82f6 !important; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06) !important; +} + +/* 表单标签样式 */ +.auth-label { + @apply block text-sm font-medium text-gray-700 mb-2; +} + +/* 链接样式 */ +.auth-link { + @apply text-gray-600 hover:text-sky-600 transition-colors mx-2; +} + +/* 卡片样式 */ +.auth-card { + @apply bg-white/95 backdrop-blur-sm shadow-2xl rounded-2xl border border-white/20; +} + +/* 标题样式 */ +.auth-title { + @apply text-2xl font-bold text-gray-900 mb-2; +} + +.auth-subtitle { + @apply text-gray-600 text-sm; +} + +/* 响应式优化 */ +@media (max-width: 640px) { + .auth-input :deep(.el-input__wrapper) { + border-radius: 6px !important; + } + + .auth-button :deep(.el-button--primary) { + border-radius: 6px !important; + } +} + +/* 动画效果 */ +.auth-fade-in { + animation: fadeIn 0.5s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* 加载状态样式 */ +.auth-loading { + @apply opacity-75 pointer-events-none; +} + +/* 错误状态样式 */ +.auth-error :deep(.el-input__wrapper) { + border-color: #ef4444 !important; + box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.1) !important; +} + +/* 成功状态样式 */ +.auth-success :deep(.el-input__wrapper) { + border-color: #10b981 !important; + box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.1) !important; +} diff --git a/src/assets/styles/index.css b/src/assets/styles/index.css new file mode 100644 index 0000000..0b68b82 --- /dev/null +++ b/src/assets/styles/index.css @@ -0,0 +1,460 @@ +@import "tailwindcss"; +@import "./performance.css"; +@import "./auth.css"; +@import "./list-pages.css"; + +/* 全局样式重置 */ +* { + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* background: #f8fafc; */ +} + +/* Element Plus 样式覆盖 */ +.el-button { + font-weight: 500; +} + +.el-button--primary { + background-color: #3b82f6; + border-color: #3b82f6; +} + +.el-button--primary:hover { + background-color: #2563eb; + border-color: #2563eb; +} + +.el-button--primary:focus { + background-color: #1d4ed8; + border-color: #1d4ed8; +} + +.el-input__inner { + border-radius: 0.375rem; +} + +.el-input__inner:focus { + border-color: #3b82f6; +} + +.el-form-item__label { + font-weight: 500; + color: #374151; +} + +.el-message { + border-radius: 0.5rem; +} + +.el-message--success { + background-color: #f0fdf4; + border-color: #bbf7d0; +} + +.el-message--error { + background-color: #fef2f2; + border-color: #fecaca; +} + +.el-message--warning { + background-color: #fffbeb; + border-color: #fed7aa; +} + +.el-message--info { + background-color: #eff6ff; + border-color: #bfdbfe; +} + +.el-dropdown-menu { + border-radius: 0.5rem; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.el-dropdown-menu__item { + padding: 0.5rem 1rem; +} + +.el-dropdown-menu__item:hover { + background-color: #f3f4f6; +} + +.el-avatar { + background-color: #3b82f6; + color: white; + font-weight: 500; +} + +/* 自定义滚动条 */ +::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +::-webkit-scrollbar-track { + background: #f1f5f9; + border-radius: 6px; +} + +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 6px; +} + +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* Firefox 滚动条样式 */ +* { + scrollbar-width: auto; + scrollbar-color: #cbd5e1 #f1f5f9; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .el-dropdown-menu { + min-width: 120px; + } + + .el-button { + padding: 0.5rem 1rem; + font-size: 0.875rem; + } +} + +/* 动画效果 */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.3s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.slide-enter-active, +.slide-leave-active { + transition: transform 0.3s ease; +} + +.slide-enter-from { + transform: translateX(-100%); +} + +.slide-leave-to { + transform: translateX(100%); +} + +/* 加载动画 */ +.loading-spinner { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid #f3f4f6; + border-radius: 50%; + border-top-color: #3b82f6; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* 工具类 */ +.text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-ellipsis-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.text-ellipsis-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* 阴影效果 */ +.shadow-soft { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.shadow-medium { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.shadow-large { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +/* 边框效果 */ +.border-soft { + border: 1px solid #e5e7eb; +} + +.border-medium { + border: 1px solid #d1d5db; +} + +/* 圆角效果 */ +.rounded-soft { + border-radius: 0.375rem; +} + +.rounded-medium { + border-radius: 0.5rem; +} + +.rounded-large { + border-radius: 0.75rem; +} + +/* 间距工具类 */ +.space-y-soft > * + * { + margin-top: 0.5rem; +} + +.space-y-medium > * + * { + margin-top: 1rem; +} + +.space-y-large > * + * { + margin-top: 1.5rem; +} + +.space-x-soft > * + * { + margin-left: 0.5rem; +} + +.space-x-medium > * + * { + margin-left: 1rem; +} + +.space-x-large > * + * { + margin-left: 1.5rem; +} + +/* 状态颜色 */ +.status-success { + color: #059669; + background-color: #d1fae5; +} + +.status-error { + color: #dc2626; + background-color: #fee2e2; +} + +.status-warning { + color: #d97706; + background-color: #fef3c7; +} + +.status-info { + color: #2563eb; + background-color: #dbeafe; +} + +/* 卡片样式 */ +.card { + background-color: white; + border-radius: 0.5rem; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + padding: 1.5rem; +} + +.card-header { + border-bottom: 1px solid #e5e7eb; + padding-bottom: 1rem; + margin-bottom: 1rem; +} + +.card-title { + font-size: 1.125rem; + font-weight: 600; + color: #111827; + margin: 0; +} + +.card-subtitle { + font-size: 0.875rem; + color: #6b7280; + margin: 0.25rem 0 0 0; +} + +/* 表格样式 */ +.table-container { + background-color: white; + border-radius: 0.5rem; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + overflow: hidden; +} + +.table-header { + background-color: #f9fafb; + border-bottom: 1px solid #e5e7eb; + padding: 1rem 1.5rem; +} + +.table-title { + font-size: 1.125rem; + font-weight: 600; + color: #111827; + margin: 0; +} + +/* 表单样式 */ +.form-section { + background-color: white; + border-radius: 0.5rem; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + padding: 1.5rem; + margin-bottom: 1.5rem; +} + +.form-section-title { + font-size: 1.125rem; + font-weight: 600; + color: #111827; + margin: 0 0 1rem 0; + padding-bottom: 0.5rem; + border-bottom: 1px solid #e5e7eb; +} + +/* 按钮组样式 */ +.button-group { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.button-group-vertical { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +/* 徽章样式 */ +.badge { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + border-radius: 9999px; +} + +.badge-success { + background-color: #d1fae5; + color: #059669; +} + +.badge-error { + background-color: #fee2e2; + color: #dc2626; +} + +.badge-warning { + background-color: #fef3c7; + color: #d97706; +} + +.badge-info { + background-color: #dbeafe; + color: #2563eb; +} + +.badge-gray { + background-color: #f3f4f6; + color: #6b7280; +} + +/* 空状态样式 */ +.empty-state { + text-align: center; + padding: 3rem 1.5rem; +} + +.empty-state-icon { + width: 4rem; + height: 4rem; + margin: 0 auto 1rem; + color: #9ca3af; +} + +.empty-state-title { + font-size: 1.125rem; + font-weight: 500; + color: #374151; + margin: 0 0 0.5rem 0; +} + +.empty-state-description { + font-size: 0.875rem; + color: #6b7280; + margin: 0; +} + +/* 响应式工具类 */ +@media (max-width: 640px) { + .sm\:hidden { + display: none; + } + + .sm\:block { + display: block; + } + + .sm\:flex { + display: flex; + } +} + +@media (max-width: 768px) { + .md\:hidden { + display: none; + } + + .md\:block { + display: block; + } + + .md\:flex { + display: flex; + } +} + +@media (max-width: 1024px) { + .lg\:hidden { + display: none; + } + + .lg\:block { + display: block; + } + + .lg\:flex { + display: flex; + } +} diff --git a/src/assets/styles/list-pages.css b/src/assets/styles/list-pages.css new file mode 100644 index 0000000..b28efec --- /dev/null +++ b/src/assets/styles/list-pages.css @@ -0,0 +1,609 @@ +/* 列表页通用样式 - 科技感、简约、高级设计 */ + +/* ===== 页面容器 ===== */ +.list-page-container { + min-height: 100vh; + position: relative; +} + +/* ===== 主卡片容器 ===== */ +.list-page-card { + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(226, 232, 240, 0.8); + border-radius: 16px; + box-shadow: + 0 4px 6px -1px rgba(0, 0, 0, 0.05), + 0 10px 15px -3px rgba(0, 0, 0, 0.03), + 0 0 0 1px rgba(255, 255, 255, 0.1); + overflow: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + padding: 24px; +} + +.list-page-card:hover { + box-shadow: + 0 10px 25px -3px rgba(0, 0, 0, 0.08), + 0 4px 6px -2px rgba(0, 0, 0, 0.04), + 0 0 0 1px rgba(255, 255, 255, 0.15); +} + +/* ===== 页面头部 ===== */ +.list-page-header { + padding: 24px 24px 18px; + border-bottom: 1px solid rgba(226, 232, 240, 0.6); + background: linear-gradient(135deg, rgba(248, 250, 252, 0.8) 0%, rgba(241, 245, 249, 0.8) 100%); +} + +.list-page-title { + font-size: 20px; + font-weight: 700; + color: #1e293b; + margin: 0 0 8px 0; + letter-spacing: -0.025em; + line-height: 1.2; +} + +.list-page-subtitle { + font-size: 14px; + color: #64748b; + margin: 0; + font-weight: 400; + line-height: 1.5; +} + +.list-page-actions { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +/* ===== 筛选区域 ===== */ +.list-page-filters { + padding: 24px 32px; + background: rgba(248, 250, 252, 0.5); + border-bottom: 1px solid rgba(226, 232, 240, 0.4); +} + +.filter-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 20px; + margin-bottom: 20px; +} + +.filter-item { + display: flex; + flex-direction: column; +} + +.filter-label { + font-size: 14px; + font-weight: 600; + color: #475569; + margin-bottom: 8px; + letter-spacing: 0.025em; +} + +.filter-actions { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 20px; + border-top: 1px solid rgba(226, 232, 240, 0.4); +} + +.filter-stats { + font-size: 14px; + color: #64748b; + font-weight: 500; +} + +.filter-buttons { + display: flex; + gap: 12px; +} + +/* ===== 表格区域 ===== */ +.list-page-table { + /* padding: 32px; */ +} + +.table-wrapper { + border-radius: 12px; + overflow: hidden; + border: 1px solid rgba(226, 232, 240, 0.6); + background: rgba(255, 255, 255, 0.8); +} + +/* ===== 分页区域 ===== */ +.list-page-pagination { + padding: 24px 32px; + border-top: 1px solid rgba(226, 232, 240, 0.4); + background: rgba(248, 250, 252, 0.3); + display: flex; + justify-content: center; +} + +/* ===== Element Plus 组件样式覆盖 ===== */ + +/* 按钮样式 */ +.list-page-container .el-button { + border-radius: 8px; + font-weight: 600; + letter-spacing: 0.025em; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + border: 1px solid transparent; +} + +.list-page-container .el-button--primary { + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + border-color: #3b82f6; + box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2); +} + +.list-page-container .el-button--primary:hover { + background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); + border-color: #2563eb; + box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3); + transform: translateY(-1px); +} + +.list-page-container .el-button--default { + background: rgba(255, 255, 255, 0.9); + border-color: rgba(226, 232, 240, 0.8); + color: #475569; +} + +.list-page-container .el-button--default:hover { + background: rgba(255, 255, 255, 1); + border-color: #cbd5e1; + color: #1e293b; + transform: translateY(-1px); +} + +.list-page-container .el-button--danger { + background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); + border-color: #ef4444; + box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2); +} + +.list-page-container .el-button--danger:hover { + background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%); + border-color: #dc2626; + box-shadow: 0 4px 8px rgba(239, 68, 68, 0.3); + transform: translateY(-1px); +} + +/* 输入框样式 */ +.list-page-container .el-input__wrapper { + border-radius: 8px; + border: 1px solid rgba(226, 232, 240, 0.8); + background: rgba(255, 255, 255, 0.9); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.list-page-container .el-input__wrapper:hover { + border-color: #cbd5e1; + background: rgba(255, 255, 255, 1); +} + +.list-page-container .el-input__wrapper.is-focus { + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + background: rgba(255, 255, 255, 1); +} + +.list-page-container .el-input__inner { + color: #1e293b; + font-weight: 500; +} + +.list-page-container .el-input__inner::placeholder { + color: #94a3b8; + font-weight: 400; +} + +/* 选择器样式 */ +.list-page-container .el-select .el-input__wrapper { + border-radius: 8px; +} + +.list-page-container .el-select-dropdown { + border-radius: 12px; + border: 1px solid rgba(226, 232, 240, 0.8); + box-shadow: + 0 10px 25px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + background: rgba(255, 255, 255, 0.95); +} + +.list-page-container .el-select-dropdown__item { + color: #475569; + font-weight: 500; + transition: all 0.2s ease; +} + +.list-page-container .el-select-dropdown__item:hover { + background: rgba(59, 130, 246, 0.1); + color: #3b82f6; +} + +.list-page-container .el-select-dropdown__item.selected { + background: rgba(59, 130, 246, 0.15); + color: #3b82f6; + font-weight: 600; +} + +/* 表格样式 */ +.list-page-container .el-table { + background: transparent; + border: none; +} + +.list-page-container .el-table th { + background: rgba(248, 250, 252, 0.8); + border-bottom: 1px solid rgba(226, 232, 240, 0.6); + color: #475569; + font-weight: 600; + font-size: 14px; + letter-spacing: 0.025em; + padding: 16px 12px; +} + +.list-page-container .el-table td { + border-bottom: 1px solid rgba(226, 232, 240, 0.3); + /* padding: 16px 12px; */ + color: #1e293b; + font-weight: 500; +} + +.list-page-container .el-table tr:hover > td { + background: rgba(59, 130, 246, 0.02); +} + +.list-page-container .el-table--striped .el-table__body tr.el-table__row--striped td { + background: rgba(248, 250, 252, 0.3); +} + +.list-page-container .el-table--striped .el-table__body tr.el-table__row--striped:hover td { + background: rgba(59, 130, 246, 0.04); +} + +/* 标签样式 */ +.list-page-container .el-tag { + border-radius: 6px; + font-weight: 600; + font-size: 12px; + letter-spacing: 0.025em; + border: none; + padding: 4px 8px; +} + +.list-page-container .el-tag--success { + background: rgba(34, 197, 94, 0.1); + color: #16a34a; +} + +.list-page-container .el-tag--danger { + background: rgba(239, 68, 68, 0.1); + color: #dc2626; +} + +.list-page-container .el-tag--warning { + background: rgba(245, 158, 11, 0.1); + color: #d97706; +} + +.list-page-container .el-tag--info { + background: rgba(59, 130, 246, 0.1); + color: #3b82f6; +} + +/* 分页样式 */ +.list-page-container .el-pagination { + --el-pagination-bg-color: transparent; + --el-pagination-text-color: #475569; + --el-pagination-border-radius: 8px; + --el-pagination-button-color: #64748b; + --el-pagination-button-bg-color: rgba(255, 255, 255, 0.8); + --el-pagination-button-disabled-color: #cbd5e1; + --el-pagination-button-disabled-bg-color: rgba(248, 250, 252, 0.5); + --el-pagination-hover-color: #3b82f6; + --el-pagination-hover-bg-color: rgba(59, 130, 246, 0.1); +} + +.list-page-container .el-pagination .el-pager li { + border-radius: 8px; + font-weight: 600; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +.list-page-container .el-pagination .el-pager li:hover { + transform: translateY(-1px); +} + +.list-page-container .el-pagination .el-pager li.is-active { + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + color: white; + box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); +} + +/* 卡片样式 */ +.list-page-container .el-card { + border-radius: 12px; + border: 1px solid rgba(226, 232, 240, 0.6); + background: rgba(255, 255, 255, 0.9); + box-shadow: + 0 2px 4px rgba(0, 0, 0, 0.05), + 0 1px 2px rgba(0, 0, 0, 0.03); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.list-page-container .el-card:hover { + box-shadow: + 0 4px 8px rgba(0, 0, 0, 0.08), + 0 2px 4px rgba(0, 0, 0, 0.05); + transform: translateY(-1px); +} + +.list-page-container .el-card__body { + padding: 20px; +} + +/* 加载状态 */ +.list-page-container .el-loading-mask { + background: rgba(255, 255, 255, 0.95); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .list-page-container { + padding: 16px; + } + + .list-page-header { + padding: 24px 20px 20px; + } + + .list-page-header .flex.justify-between { + flex-direction: column; + gap: 16px; + } + + .list-page-title { + font-size: 24px; + } + + .list-page-actions { + width: 100%; + justify-content: flex-start; + gap: 8px; + } + + .list-page-actions .el-button { + flex: 1; + min-width: 0; + flex-basis: calc(50% - 4px); + } + + .list-page-actions .el-button:last-child:nth-child(odd) { + flex-basis: 100%; + } + + .list-page-filters { + padding: 20px; + } + + .filter-grid { + grid-template-columns: 1fr; + gap: 16px; + } + + .list-page-table { + padding: 0 20px 20px; + } + + .list-page-pagination { + padding: 20px; + } + + .filter-actions { + flex-direction: column; + gap: 16px; + align-items: stretch; + } + + .filter-buttons { + justify-content: center; + } + + /* ===== 移动端表格优化 ===== */ + /* 表格容器允许横向滚动 */ + .table-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + /* 隐藏滚动条但保持滚动功能 */ + scrollbar-width: thin; + } + + /* 移除固定列效果 - 通过覆盖 Element Plus 的固定列样式 */ + .list-page-container .el-table .el-table__fixed, + .list-page-container .el-table .el-table__fixed-right { + position: static !important; + box-shadow: none !important; + background-color: transparent !important; + } + + /* 固定列的表头和表体都改为静态定位 */ + .list-page-container .el-table .el-table__fixed-header-wrapper, + .list-page-container .el-table .el-table__fixed-body-wrapper, + .list-page-container .el-table .el-table__fixed-footer-wrapper { + position: static !important; + } + + /* 表格单元格在移动端优化 */ + .list-page-container .el-table th, + .list-page-container .el-table td { + padding: 12px 8px !important; + font-size: 13px; + } + + /* 操作按钮组在移动端改为紧凑布局 */ + .list-page-container .el-table .el-table__cell .flex.gap-2, + .list-page-container .el-table .el-table__cell .flex.items-center, + .list-page-container .el-table .el-table__cell .flex.space-x-2 { + flex-wrap: wrap; + gap: 6px !important; + } + + /* 操作按钮在移动端缩小 */ + .list-page-container .el-table .el-button--small { + padding: 6px 10px; + font-size: 12px; + min-width: auto; + } + + /* 表格列宽度优化 - 允许更灵活的宽度 */ + .list-page-container .el-table .el-table__cell { + min-width: 80px; + } + + /* 操作列宽度自适应,不设置最小宽度 */ + .list-page-container .el-table .el-table__cell[data-label="操作"], + .list-page-container .el-table th:last-child, + .list-page-container .el-table td:last-child { + min-width: auto !important; + width: auto !important; + } + + /* 隐藏部分次要列在移动端 - 通过类名控制 */ + .list-page-container .el-table .el-table__cell.hidden-mobile { + display: none; + } + + /* 操作按钮在移动端自动换行,避免溢出 */ + .list-page-container .el-table .el-table__cell .el-button + .el-button { + margin-left: 0; + } + + /* 表格在移动端允许横向滚动 */ + .list-page-container .el-table { + min-width: 600px; + } + + /* 操作列在移动端不设置最小宽度,允许换行 */ + .list-page-container .el-table .el-table__cell[data-label="操作"] { + white-space: normal; + word-break: break-word; + } + + /* 操作按钮组在移动端更紧凑 */ + .list-page-container .el-table .el-table__cell .flex { + justify-content: flex-start; + } + + /* 下拉菜单按钮在移动端优化 */ + .list-page-container .el-table .el-dropdown .el-button { + padding: 6px 10px; + } +} + +/* 超小屏幕进一步优化 */ +@media (max-width: 480px) { + .list-page-container { + padding: 12px; + } + + .list-page-header { + padding: 16px 12px 12px; + } + + .list-page-title { + font-size: 20px; + } + + .list-page-actions { + flex-direction: column; + gap: 8px; + } + + .list-page-actions .el-button { + flex-basis: 100%; + width: 100%; + } + + .list-page-filters { + padding: 16px 12px; + } + + .list-page-table { + padding: 0 12px 12px; + } + + /* 表格单元格进一步缩小 */ + .list-page-container .el-table th, + .list-page-container .el-table td { + padding: 10px 6px !important; + font-size: 12px; + } + + /* 操作按钮更紧凑 */ + .list-page-container .el-table .el-button--small { + padding: 4px 8px; + font-size: 11px; + } + + /* 操作按钮组更紧凑 */ + .list-page-container .el-table .el-table__cell .flex.gap-2, + .list-page-container .el-table .el-table__cell .flex.items-center { + gap: 4px !important; + } +} + +/* 动画效果 */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.list-page-card { + animation: fadeInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* 性能优化:减少动画效果 */ +@media (prefers-reduced-motion: reduce) { + .list-page-card { + animation: none !important; + } +} + +/* 科技感装饰元素 */ +.list-page-container::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent 0%, rgba(59, 130, 246, 0.3) 50%, transparent 100%); + z-index: 1; +} + +.list-page-container::after { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 1px; + height: 100vh; + background: linear-gradient(180deg, transparent 0%, rgba(59, 130, 246, 0.1) 50%, transparent 100%); + z-index: 1; +} diff --git a/src/assets/styles/performance.css b/src/assets/styles/performance.css new file mode 100644 index 0000000..1b7f78b --- /dev/null +++ b/src/assets/styles/performance.css @@ -0,0 +1,186 @@ +/* 渲染性能优化样式文件 */ + +/* 1. 减少动画效果 - 尊重用户偏好 */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* 2. 低端设备优化 */ +@media (max-width: 768px), (max-device-pixel-ratio: 1) { + /* 简化背景渐变 */ + .bg-gradient-to-br, + .bg-gradient-to-tl, + .bg-gradient-to-tr, + .bg-gradient-to-bl { + background: #f8fafc !important; + } + + /* 简化阴影效果 */ + .shadow-soft, + .shadow-medium, + .shadow-large { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important; + } + + /* 移除复杂的边框效果 */ + .border-soft, + .border-medium { + border: 1px solid #e5e7eb !important; + } +} + +/* 3. 硬件加速优化 */ +.gpu-accelerated { + transform: translateZ(0); + will-change: transform; +} + +/* 4. 简化backdrop-filter的替代方案 */ +.glass-effect { + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(226, 232, 240, 0.6); +} + +.glass-effect-light { + background: rgba(255, 255, 255, 0.9); + border: 1px solid rgba(226, 232, 240, 0.4); +} + +/* 5. 优化过渡效果 */ +.optimized-transition { + transition: all 0.2s ease; + will-change: transform, opacity; +} + +/* 6. 移动端性能优化 */ +@media (max-width: 640px) { + /* 减少复杂的CSS计算 */ + .mobile-optimized { + transform: none !important; + filter: none !important; + } + + /* 简化字体渲染 */ + body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } +} + +/* 7. 高DPI屏幕优化 */ +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + /* 为高分辨率屏幕优化 */ + .high-dpi-optimized { + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + } +} + +/* 8. 减少重绘和重排 */ +.layout-stable { + contain: layout style paint; +} + +/* 9. 优化滚动性能 */ +.smooth-scroll { + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; +} + +/* 10. 减少不必要的动画 */ +@media (max-width: 1024px) { + .desktop-only-animation { + animation: none !important; + } +} + +/* 11. 优化图片和媒体 */ +img, video, canvas { + max-width: 100%; + height: auto; +} + +/* 12. 减少CSS选择器复杂度 */ +.simple-selector { + /* 使用简单的选择器 */ +} + +/* 13. 优化字体加载 */ +.font-optimized { + font-display: swap; +} + +/* 14. 减少阴影复杂度 */ +.simple-shadow { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* 15. 优化边框半径 */ +.optimized-radius { + border-radius: 8px; +} + +/* 16. 减少透明度计算 */ +.solid-bg { + background: #ffffff; +} + +/* 17. 优化渐变效果 */ +.simple-gradient { + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); +} + +/* 18. 减少变换复杂度 */ +.simple-transform { + transform: translateY(-2px); +} + +/* 19. 优化伪元素 */ +.optimized-pseudo::before, +.optimized-pseudo::after { + content: ''; + position: absolute; + pointer-events: none; +} + +/* 20. 减少媒体查询嵌套 */ +@media (max-width: 768px) { + .mobile-simple { + /* 简化的移动端样式 */ + } +} + +/* 21. 性能等级模式样式 */ +.low-performance-mode { + /* 低端设备:禁用复杂效果 */ +} + +.medium-performance-mode { + /* 中端设备:适度优化 */ +} + +.high-performance-mode { + /* 高端设备:启用所有功能 */ +} + +/* 22. 硬件加速降级样式 */ +.no-hardware-acceleration { + /* 不支持硬件加速时的降级样式 */ +} + +/* 23. backdrop-filter降级样式 */ +.no-backdrop-filter { + /* 不支持backdrop-filter时的降级样式 */ +} + +/* 24. 减少动画模式样式 */ +.reduced-motion { + /* 用户偏好减少动画时的样式 */ +} diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue new file mode 100644 index 0000000..eff59f1 --- /dev/null +++ b/src/components/HelloWorld.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/components/TheWelcome.vue b/src/components/TheWelcome.vue new file mode 100644 index 0000000..fe48afc --- /dev/null +++ b/src/components/TheWelcome.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/components/WelcomeItem.vue b/src/components/WelcomeItem.vue new file mode 100644 index 0000000..ac366d0 --- /dev/null +++ b/src/components/WelcomeItem.vue @@ -0,0 +1,86 @@ + + + diff --git a/src/components/admin/ProductApiConfigDialog.vue b/src/components/admin/ProductApiConfigDialog.vue new file mode 100644 index 0000000..e0d336a --- /dev/null +++ b/src/components/admin/ProductApiConfigDialog.vue @@ -0,0 +1,501 @@ + + + + + diff --git a/src/components/admin/ProductDocumentationDialog.vue b/src/components/admin/ProductDocumentationDialog.vue new file mode 100644 index 0000000..0c6aa2b --- /dev/null +++ b/src/components/admin/ProductDocumentationDialog.vue @@ -0,0 +1,671 @@ + + + + + diff --git a/src/components/admin/ProductFormDialog.vue b/src/components/admin/ProductFormDialog.vue new file mode 100644 index 0000000..61357c5 --- /dev/null +++ b/src/components/admin/ProductFormDialog.vue @@ -0,0 +1,1169 @@ + + + + + diff --git a/src/components/auth/PermissionGuard.vue b/src/components/auth/PermissionGuard.vue new file mode 100644 index 0000000..85f0a50 --- /dev/null +++ b/src/components/auth/PermissionGuard.vue @@ -0,0 +1,108 @@ + + + + + + diff --git a/src/components/common/AppLoading.vue b/src/components/common/AppLoading.vue new file mode 100644 index 0000000..5ee6c48 --- /dev/null +++ b/src/components/common/AppLoading.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/src/components/common/BusinessConsultationDialog.vue b/src/components/common/BusinessConsultationDialog.vue new file mode 100644 index 0000000..fef04f7 --- /dev/null +++ b/src/components/common/BusinessConsultationDialog.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/src/components/common/CertificationBanner.vue b/src/components/common/CertificationBanner.vue new file mode 100644 index 0000000..7a5453c --- /dev/null +++ b/src/components/common/CertificationBanner.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/components/common/CertificationNotice.vue b/src/components/common/CertificationNotice.vue new file mode 100644 index 0000000..6b390df --- /dev/null +++ b/src/components/common/CertificationNotice.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/src/components/common/CodeDisplay.vue b/src/components/common/CodeDisplay.vue new file mode 100644 index 0000000..9bfa7df --- /dev/null +++ b/src/components/common/CodeDisplay.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/src/components/common/CustomSteps.vue b/src/components/common/CustomSteps.vue new file mode 100644 index 0000000..ae386dd --- /dev/null +++ b/src/components/common/CustomSteps.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/src/components/common/DanmakuBar.vue b/src/components/common/DanmakuBar.vue new file mode 100644 index 0000000..98ffcbe --- /dev/null +++ b/src/components/common/DanmakuBar.vue @@ -0,0 +1,379 @@ + + + + + diff --git a/src/components/common/ExportDialog.md b/src/components/common/ExportDialog.md new file mode 100644 index 0000000..093b555 --- /dev/null +++ b/src/components/common/ExportDialog.md @@ -0,0 +1,205 @@ +# ExportDialog 导出弹窗组件 + +## 概述 + +`ExportDialog` 是一个通用的导出数据弹窗组件,支持多种筛选条件和导出格式。该组件被设计为可复用的公共组件,用于各个管理页面的数据导出功能。 + +## 功能特性 + +- ✅ **企业选择**:支持多选、本地搜索(直接加载所有已认证企业) +- ✅ **产品选择**:支持多选、搜索、异步加载 +- ✅ **充值类型选择**:支持单选筛选 +- ✅ **状态选择**:支持单选筛选 +- ✅ **时间范围**:支持日期时间范围选择 +- ✅ **导出格式**:支持Excel和CSV格式 +- ✅ **异步搜索**:产品名称支持实时搜索 +- ✅ **批量加载**:一次性加载1000条数据,提升性能 + +## Props + +| 属性名 | 类型 | 默认值 | 说明 | +|--------|------|--------|------| +| `modelValue` | Boolean | false | 控制弹窗显示/隐藏 | +| `title` | String | '导出数据' | 弹窗标题 | +| `loading` | Boolean | false | 导出按钮加载状态 | +| `showCompanySelect` | Boolean | true | 是否显示企业选择 | +| `showProductSelect` | Boolean | true | 是否显示产品选择 | +| `showRechargeTypeSelect` | Boolean | false | 是否显示充值类型选择 | +| `showStatusSelect` | Boolean | false | 是否显示状态选择 | +| `showDateRange` | Boolean | true | 是否显示时间范围选择 | + +## Events + +| 事件名 | 参数 | 说明 | +|--------|------|------| +| `update:modelValue` | Boolean | 弹窗显示状态变化 | +| `confirm` | Object | 确认导出,返回筛选条件对象 | +| `cancel` | - | 取消导出 | + +## 筛选条件对象结构 + +```javascript +{ + companyIds: [], // 企业ID数组 + productIds: [], // 产品ID数组 + rechargeType: '', // 充值类型 + status: '', // 状态 + dateRange: [], // 时间范围 [startTime, endTime] + format: 'excel' // 导出格式 'excel' | 'csv' +} +``` + +## 使用示例 + +### 1. API调用记录导出 + +```vue + + + +``` + +### 2. 充值记录导出 + +```vue + + + +``` + +### 3. 钱包交易记录导出 + +```vue + + + +``` + +## 技术实现 + +### 数据加载优化 + +- **企业数据**:一次性加载所有已认证企业(最多1000条),支持本地搜索 +- **产品数据**:支持异步搜索,减少网络请求 +- **缓存机制**:已加载的数据会被缓存,避免重复请求 + +### 性能优化 + +- **企业数据**:一次性加载,本地搜索,无需网络请求 +- **产品搜索**:防抖处理,减少API调用 +- **懒加载**:下拉框首次打开时才加载数据 +- **内存管理**:组件销毁时清理事件监听器 + +### 错误处理 + +- **网络错误**:API请求失败时显示友好提示 +- **数据验证**:导出前验证必要参数 +- **用户反馈**:操作成功/失败都有明确提示 + +## 样式定制 + +组件使用 Tailwind CSS 进行样式设计,支持以下自定义: + +```css +/* 自定义弹窗样式 */ +.export-dialog { + /* 自定义样式 */ +} + +/* 自定义选择器样式 */ +.export-dialog .el-select { + /* 自定义样式 */ +} +``` + +## 注意事项 + +1. **API依赖**:组件依赖 `userApi` 和 `productApi`,确保这些API已正确配置 +2. **权限控制**:企业列表只加载已认证用户(`is_certified: true`) +3. **数据格式**:时间范围使用 `YYYY-MM-DD HH:mm:ss` 格式 +4. **文件下载**:导出文件会自动下载,文件名包含时间戳 + +## 更新日志 + +### v1.0.0 +- ✅ 初始版本发布 +- ✅ 支持企业、产品、时间范围筛选 +- ✅ 支持Excel和CSV格式导出 +- ✅ 异步搜索和批量加载 +- ✅ 完整的错误处理和用户反馈 diff --git a/src/components/common/ExportDialog.vue b/src/components/common/ExportDialog.vue new file mode 100644 index 0000000..ff40ae7 --- /dev/null +++ b/src/components/common/ExportDialog.vue @@ -0,0 +1,321 @@ + + + + + + + diff --git a/src/components/common/FileUpload.md b/src/components/common/FileUpload.md new file mode 100644 index 0000000..3221b21 --- /dev/null +++ b/src/components/common/FileUpload.md @@ -0,0 +1,138 @@ +# FileUpload 文件上传组件 + +一个通用的文件上传组件,支持拖拽上传、文件预览、删除等功能。 + +## 功能特性 + +- ✅ 拖拽上传 +- ✅ 文件类型验证 +- ✅ 文件大小限制 +- ✅ 图片预览 +- ✅ 文件删除 +- ✅ 自定义标题和描述 +- ✅ 响应式设计 +- ✅ v-model 支持 + +## 基本用法 + +```vue + + + +``` + +## 自定义配置 + +```vue + +``` + +## 支持的文件类型 + +### 图片文件 +```vue + +``` + +### 文档文件 +```vue + +``` + +### 所有文件类型 +```vue + +``` + +## Props + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| accept | String | 'image/jpeg,image/jpg,image/png' | 接受的文件类型 | +| maxSize | Number | 4 | 文件大小限制(MB) | +| title | String | '' | 上传区域标题 | +| description | String | '' | 上传区域描述 | +| disabled | Boolean | false | 是否禁用 | +| modelValue | File/null | null | 当前文件(v-model) | +| previewUrl | String | '' | 文件预览URL | + +## Events + +| 事件名 | 参数 | 说明 | +|--------|------|------| +| update:modelValue | file | 文件变化时触发 | +| change | file | 文件选择时触发 | +| remove | - | 文件删除时触发 | + +## 样式定制 + +组件使用 scoped 样式,如需自定义样式,可以通过以下方式: + +```vue + +``` + +## 注意事项 + +1. 组件会自动阻止自动上传,需要手动处理文件上传逻辑 +2. 图片文件会自动生成预览,其他文件类型显示文件图标 +3. 文件验证失败时会显示错误提示 +4. 组件支持响应式设计,在移动端会自动调整布局 \ No newline at end of file diff --git a/src/components/common/FileUpload.vue b/src/components/common/FileUpload.vue new file mode 100644 index 0000000..7ccf123 --- /dev/null +++ b/src/components/common/FileUpload.vue @@ -0,0 +1,371 @@ + + + + + diff --git a/src/components/common/FilterItem.vue b/src/components/common/FilterItem.vue new file mode 100644 index 0000000..097e919 --- /dev/null +++ b/src/components/common/FilterItem.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/components/common/FilterSection.vue b/src/components/common/FilterSection.vue new file mode 100644 index 0000000..3a454f8 --- /dev/null +++ b/src/components/common/FilterSection.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/components/common/FloatingCustomerService.vue b/src/components/common/FloatingCustomerService.vue new file mode 100644 index 0000000..467a954 --- /dev/null +++ b/src/components/common/FloatingCustomerService.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/components/common/ListPageLayout.vue b/src/components/common/ListPageLayout.vue new file mode 100644 index 0000000..00f4b89 --- /dev/null +++ b/src/components/common/ListPageLayout.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/components/common/MarkdownEditor.vue b/src/components/common/MarkdownEditor.vue new file mode 100644 index 0000000..90fa8c1 --- /dev/null +++ b/src/components/common/MarkdownEditor.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/src/components/common/README.md b/src/components/common/README.md new file mode 100644 index 0000000..67583ef --- /dev/null +++ b/src/components/common/README.md @@ -0,0 +1,314 @@ +# 通用组件库 + +本项目包含了一系列可复用的通用组件,用于提高开发效率和保持界面一致性。 + +## 组件列表 + +### FileUpload 文件上传组件 + +一个功能完整的文件上传组件,支持拖拽上传、文件预览、删除等功能。 + +**特性:** +- ✅ 拖拽上传 +- ✅ 文件类型验证 +- ✅ 文件大小限制 +- ✅ 图片预览 +- ✅ 文件删除 +- ✅ 自定义标题和描述 +- ✅ 响应式设计 +- ✅ v-model 支持 + +**使用示例:** +```vue + + + +``` + +**详细文档:** [FileUpload.md](./FileUpload.md) + +## 组件开发规范 + +### 1. 文件命名 +- 组件文件使用 PascalCase 命名,如 `FileUpload.vue` +- 文档文件使用 PascalCase + .md 命名,如 `FileUpload.md` + +### 2. 组件结构 +```vue + + + + + +``` + +### 3. Props 规范 +- 使用 `defineProps` 定义 props +- 为每个 prop 添加类型和默认值 +- 添加详细的注释说明 + +### 4. Events 规范 +- 使用 `defineEmits` 定义事件 +- 事件名使用 camelCase +- 提供有意义的事件参数 + +### 5. 样式规范 +- 使用 scoped 样式 +- 遵循 BEM 命名规范 +- 支持响应式设计 +- 使用 CSS 变量便于主题定制 + +### 6. 文档规范 +- 每个组件都要有详细的使用文档 +- 包含基本用法、Props、Events 说明 +- 提供完整的代码示例 +- 说明组件的特性和注意事项 + +## 测试 + +每个通用组件都应该有对应的测试页面,用于验证组件的各种功能和边界情况。 + +**测试页面位置:** `src/pages/ComponentNameTest.vue` + +**测试内容:** +- 基本功能测试 +- 各种配置组合测试 +- 边界情况测试 +- 响应式设计测试 + +## 使用建议 + +1. **优先使用通用组件**:在开发新功能时,优先考虑使用现有的通用组件 +2. **保持一致性**:使用通用组件可以保持界面风格的一致性 +3. **及时反馈**:如果发现通用组件的问题或需要新功能,及时反馈给团队 +4. **文档更新**:当组件功能发生变化时,及时更新相关文档 + +## 贡献指南 + +1. **创建新组件**:在 `src/components/common/` 目录下创建新组件 +2. **编写文档**:为每个新组件编写详细的使用文档 +3. **创建测试页面**:在 `src/pages/` 目录下创建对应的测试页面 +4. **更新路由**:在 `src/router/index.js` 中添加测试页面的路由 +5. **更新本文档**:在组件列表中添加新组件的说明 + +# 企业认证功能使用说明 + +## 概述 + +本功能用于在需要企业认证的页面中显示认证提示,并阻止未认证用户调用相关API接口。认证提示现在通过主布局统一管理,无需在每个页面中单独添加组件。 + +## 实现方式 + +### 1. 主布局统一管理 (`src/layouts/MainLayout.vue`) + +认证提示现在在主布局中统一管理,根据当前页面路径自动显示相应的认证提示。 + +**核心逻辑:** +- 通过 `getCurrentPageCertificationConfig` 获取当前页面的认证配置 +- 根据用户认证状态和页面路径决定是否显示提示 +- 支持自定义标题和描述信息 + +### 2. 菜单配置 (`src/constants/menu.js`) + +在菜单配置中定义需要认证的页面和相应的提示信息。 + +**配置方式:** +```javascript +// 在菜单项中添加认证标记 +{ name: '余额充值', path: '/finance/wallet', icon: CreditCard, requiresCertification: true } + +// 在页面配置中定义提示信息 +'/finance/wallet': { + title: '钱包充值', + description: '为了享受完整的充值服务,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' +} +``` + +### 3. 认证提示组件 (`src/components/common/CertificationNotice.vue`) + +可复用的认证提示组件,支持自定义标题和描述。 + +**组件属性:** +- `show`: 控制是否显示提示(Boolean) +- `title`: 自定义标题(String,可选) +- `description`: 自定义描述(String,可选) + +### 4. 认证组合函数 (`src/composables/useCertification.js`) + +提供认证相关的状态和方法,用于页面中的API调用保护。 + +**返回值:** +- `isCertified`: 用户是否已认证 +- `certificationLoading`: 认证状态检查是否加载中 +- `requiresCertification`: 当前页面是否需要认证 +- `callProtectedAPI`: 安全调用需要认证的API +- `canCallAPI`: 是否可以调用API + +## 使用方法 + +### 在页面中使用(简化版) + +现在页面中只需要关注API调用的保护,无需手动添加认证提示组件: + +1. **导入认证组合函数** +```vue + +``` + +2. **修改API调用** +```javascript +// 原来的API调用 +const response = await api.getData() + +// 修改为 +const response = await callProtectedAPI(api.getData) +if (response) { + // 处理成功响应 +} else { + // API调用被阻止,显示默认数据或提示 +} +``` + +### 添加新的需要认证的页面 + +1. **在菜单配置中添加认证标记** +```javascript +// 在 src/constants/menu.js 的 userMenuItems 中 +{ name: '新功能', path: '/new-feature', icon: NewIcon, requiresCertification: true } +``` + +2. **在认证路径列表中添加路径** +```javascript +// 在 requiresCertificationPaths 数组中 +export const requiresCertificationPaths = [ + // ... 现有路径 + '/new-feature' +] +``` + +3. **添加页面配置信息** +```javascript +// 在 getCurrentPageCertificationConfig 函数中添加 +'/new-feature': { + title: '新功能', + description: '为了使用新功能,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' +} +``` + +### 需要认证的页面路径 + +以下页面路径需要企业认证: +- `/finance/wallet` - 钱包充值 +- `/finance/recharge-records` - 充值记录 +- `/finance/transactions` - 消费记录 +- `/apis/usage` - API调用记录 +- `/apis/whitelist` - 白名单管理 + +## 技术特点 + +### 1. 统一管理 +- 认证提示在主布局中统一管理,避免重复代码 +- 页面配置集中管理,易于维护和扩展 + +### 2. 用户体验优化 +- 未认证用户仍能看到页面内容,不会被完全阻止访问 +- 根据页面功能提供个性化的认证提示信息 +- 支持用户选择关闭提示 + +### 3. 安全性 +- API调用级别的保护,防止未认证用户调用敏感接口 +- 优雅的错误处理,避免暴露系统内部错误信息 + +### 4. 可维护性 +- 模块化设计,易于扩展和维护 +- 清晰的配置方式,便于添加新的需要认证的页面 +- 统一的认证逻辑,避免重复代码 + +## 配置说明 + +### 自定义认证状态检查 + +在 `useCertification.js` 中修改 `checkCertificationStatus` 方法: + +```javascript +const checkCertificationStatus = async () => { + try { + // 调用实际的API来检查用户认证状态 + const { data } = await userApi.getCertificationStatus() + isCertified.value = data.isCertified || false + } catch (error) { + console.error('Failed to check certification status:', error) + isCertified.value = false + } finally { + certificationLoading.value = false + } +} +``` + +### 自定义提示样式 + +可以通过修改 `CertificationNotice.vue` 组件来自定义提示的样式和内容。 + +## 注意事项 + +1. **API调用保护**:使用 `callProtectedAPI` 包装所有需要认证的API调用 +2. **错误处理**:在API调用失败时检查 `canCallAPI` 状态,避免显示不必要的错误信息 +3. **用户体验**:未认证用户仍能看到页面内容,但无法进行实际操作 +4. **性能优化**:认证状态检查只在需要认证的页面进行 + +## 示例页面 + +参考以下页面的实现: +- `src/pages/finance/Wallet.vue` - 钱包充值页面 +- `src/pages/api/Usage.vue` - API调用记录页面 +- `src/pages/api/WhiteList.vue` - 白名单管理页面 + +## 迁移指南 + +如果之前已经在页面中添加了 `CertificationNotice` 组件,可以按以下步骤迁移: + +1. 从页面模板中移除 `` 组件 +2. 从页面脚本中移除 `CertificationNotice` 的导入 +3. 移除 `shouldShowCertificationNotice` 的使用 +4. 保留 `callProtectedAPI` 和 `canCallAPI` 的使用 + +认证提示现在会自动在主布局中显示,无需手动管理。 \ No newline at end of file diff --git a/src/components/common/ResponsiveActionColumn.vue b/src/components/common/ResponsiveActionColumn.vue new file mode 100644 index 0000000..a6ee09d --- /dev/null +++ b/src/components/common/ResponsiveActionColumn.vue @@ -0,0 +1,143 @@ + + + + + + diff --git a/src/components/common/RichTextEditor.vue b/src/components/common/RichTextEditor.vue new file mode 100644 index 0000000..affd06b --- /dev/null +++ b/src/components/common/RichTextEditor.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/components/common/VersionInfo.vue b/src/components/common/VersionInfo.vue new file mode 100644 index 0000000..151f9a1 --- /dev/null +++ b/src/components/common/VersionInfo.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/components/common/withCertificationCheck.js b/src/components/common/withCertificationCheck.js new file mode 100644 index 0000000..044ca07 --- /dev/null +++ b/src/components/common/withCertificationCheck.js @@ -0,0 +1,91 @@ +import { isPageRequiresCertification } from '@/constants/menu' +import CertificationNotice from './CertificationNotice.vue' + +export default function withCertificationCheck(WrappedComponent) { + return { + name: `WithCertificationCheck(${WrappedComponent.name || 'Component'})`, + components: { + CertificationNotice, + WrappedComponent + }, + data() { + return { + isCertified: false, + loading: true + } + }, + computed: { + requiresCertification() { + return isPageRequiresCertification(this.$route.path) + }, + shouldShowNotice() { + return this.requiresCertification && !this.isCertified + } + }, + async created() { + await this.checkCertificationStatus() + }, + methods: { + async checkCertificationStatus() { + try { + // 这里应该调用实际的API来检查用户认证状态 + // 暂时使用模拟数据,实际项目中需要替换为真实的API调用 + const userStore = this.$store?.state?.user + this.isCertified = userStore?.isCertified || false + } catch (error) { + console.error('Failed to check certification status:', error) + this.isCertified = false + } finally { + this.loading = false + } + }, + + // 重写需要认证的API调用方法 + async callProtectedAPI(apiMethod, ...args) { + if (!this.isCertified && this.requiresCertification) { + console.warn('API call blocked: User not certified') + return null + } + + try { + return await apiMethod(...args) + } catch (error) { + if (error.response?.status === 403) { + console.warn('API call failed: Access denied - certification required') + return null + } + throw error + } + } + }, + + render(h) { + // 如果页面不需要认证,直接渲染原组件 + if (!this.requiresCertification) { + return h(WrappedComponent, { + props: this.$attrs, + on: this.$listeners, + scopedSlots: this.$scopedSlots + }) + } + + // 如果需要认证但用户未认证,显示提示和原组件 + return h('div', [ + h(CertificationNotice, { + props: { + show: this.shouldShowNotice + } + }), + h(WrappedComponent, { + props: { + ...this.$attrs, + isCertified: this.isCertified, + callProtectedAPI: this.callProtectedAPI + }, + on: this.$listeners, + scopedSlots: this.$scopedSlots + }) + ]) + } + } +} diff --git a/src/components/layout/AppBreadcrumb.vue b/src/components/layout/AppBreadcrumb.vue new file mode 100644 index 0000000..3fe1919 --- /dev/null +++ b/src/components/layout/AppBreadcrumb.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/components/layout/AppHeader.vue b/src/components/layout/AppHeader.vue new file mode 100644 index 0000000..73581c0 --- /dev/null +++ b/src/components/layout/AppHeader.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/src/components/layout/AppSidebar.vue b/src/components/layout/AppSidebar.vue new file mode 100644 index 0000000..e53b524 --- /dev/null +++ b/src/components/layout/AppSidebar.vue @@ -0,0 +1,427 @@ + + + + + diff --git a/src/components/layout/NotificationPanel.vue b/src/components/layout/NotificationPanel.vue new file mode 100644 index 0000000..2488a1f --- /dev/null +++ b/src/components/layout/NotificationPanel.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/src/components/product/ProductCard.vue b/src/components/product/ProductCard.vue new file mode 100644 index 0000000..4709916 --- /dev/null +++ b/src/components/product/ProductCard.vue @@ -0,0 +1,453 @@ + + + + + diff --git a/src/components/statistics/ChartCard.vue b/src/components/statistics/ChartCard.vue new file mode 100644 index 0000000..12703dd --- /dev/null +++ b/src/components/statistics/ChartCard.vue @@ -0,0 +1,543 @@ + + + + + + diff --git a/src/components/statistics/StatCard.vue b/src/components/statistics/StatCard.vue new file mode 100644 index 0000000..3966315 --- /dev/null +++ b/src/components/statistics/StatCard.vue @@ -0,0 +1,408 @@ + + + + + + diff --git a/src/components/statistics/StatisticsDashboard.vue b/src/components/statistics/StatisticsDashboard.vue new file mode 100644 index 0000000..daeddea --- /dev/null +++ b/src/components/statistics/StatisticsDashboard.vue @@ -0,0 +1,382 @@ + + + + + diff --git a/src/composables/useAliyunCaptcha.js b/src/composables/useAliyunCaptcha.js new file mode 100644 index 0000000..f5b7de2 --- /dev/null +++ b/src/composables/useAliyunCaptcha.js @@ -0,0 +1,220 @@ +import { ElMessage } from 'element-plus' +import { captchaApi } from '@/api' + +// 阿里云验证码场景 ID(需与后端 config.sms.scene_id 一致;加密模式可由后端 config 下发) +const ALIYUN_CAPTCHA_SCENE_ID = import.meta.env.VITE_CAPTCHA_SCENE_ID || "wynt39to" +// 是否启用加密模式:通过环境变量 VITE_CAPTCHA_ENCRYPTED_MODE 控制,为 'true' 不加密 +const ENABLE_ENCRYPTED = import.meta.env.VITE_CAPTCHA_ENCRYPTED_MODE === 'true' + +let captchaEnabled = true +let captchaSceneId = ALIYUN_CAPTCHA_SCENE_ID +let captchaConfigLoaded = false +let captchaConfigPromise = null + +let captchaInitialised = false +let captchaReadyPromise = null +let captchaReadyResolve = null + +async function loadCaptchaConfig() { + if (captchaConfigLoaded) return + if (captchaConfigPromise) { + await captchaConfigPromise + return + } + + captchaConfigPromise = (async () => { + try { + const resp = await captchaApi.getConfig() + const configData = resp?.data?.data ?? resp?.data ?? {} + captchaEnabled = configData?.captchaEnabled !== false + captchaSceneId = configData?.sceneId || ALIYUN_CAPTCHA_SCENE_ID + captchaConfigLoaded = true + } catch { + // 配置获取失败时默认启用,避免安全能力被意外绕过 + captchaEnabled = true + captchaSceneId = ALIYUN_CAPTCHA_SCENE_ID + captchaConfigLoaded = true + } finally { + captchaConfigPromise = null + } + })() + + await captchaConfigPromise +} + +async function ensureCaptchaInit() { + await loadCaptchaConfig() + if (!captchaEnabled) return + + if (captchaInitialised || typeof window === "undefined") return + if (typeof window.initAliyunCaptcha !== "function") return + + captchaInitialised = true + window.captcha = null + window.__lastBizResponse = null + window.__onCaptchaBizSuccess = null + captchaReadyPromise = new Promise((resolve) => { + captchaReadyResolve = resolve + }) + + // 非加密模式:仅传 SceneId,不调用后端接口 + if (!ENABLE_ENCRYPTED) { + window.initAliyunCaptcha({ + SceneId: captchaSceneId, + mode: "popup", + element: "#captcha-element", + getInstance(instance) { + window.captcha = instance + if (typeof captchaReadyResolve === "function") { + captchaReadyResolve() + captchaReadyResolve = null + } + }, + captchaVerifyCallback(param) { + console.log("captchaVerifyCallback", param) + return typeof window.__captchaVerifyCallback === "function" + ? window.__captchaVerifyCallback(param) + : Promise.resolve({ + captchaResult: false, + bizResult: false, + }) + }, + onBizResultCallback(bizResult) { + if (typeof window.__onBizResultCallback === "function") { + window.__onBizResultCallback(bizResult) + } + window.__lastBizResponse = null + window.__onCaptchaBizSuccess = null + }, + slideStyle: { width: 360, height: 40 }, + language: "cn", + }) + return + } + + // 加密模式:先从后端获取 EncryptedSceneId,再初始化 + try { + const resp = await captchaApi.getEncryptedSceneId() + const encryptedSceneId = resp?.data?.data?.encryptedSceneId ?? resp?.data?.encryptedSceneId + if (!encryptedSceneId) { + ElMessage.error("获取验证码参数失败,请稍后重试") + captchaInitialised = false + captchaReadyPromise = null + captchaReadyResolve = null + return + } + window.initAliyunCaptcha({ + SceneId: captchaSceneId, + EncryptedSceneId: encryptedSceneId, + mode: "popup", + element: "#captcha-element", + getInstance(instance) { + window.captcha = instance + if (typeof captchaReadyResolve === "function") { + captchaReadyResolve() + captchaReadyResolve = null + } + }, + captchaVerifyCallback(param) { + return typeof window.__captchaVerifyCallback === "function" + ? window.__captchaVerifyCallback(param) + : Promise.resolve({ captchaResult: false, bizResult: false }) + }, + onBizResultCallback(bizResult) { + if (typeof window.__onBizResultCallback === "function") { + window.__onBizResultCallback(bizResult) + } + window.__lastBizResponse = null + window.__onCaptchaBizSuccess = null + }, + slideStyle: { width: 360, height: 40 }, + language: "cn", + }) + } catch { + ElMessage.error("获取验证码参数失败,请稍后重试") + captchaInitialised = false + captchaReadyPromise = null + captchaReadyResolve = null + } +} + +/** + * 阿里云滑块验证码通用封装。 + * 依赖 index.html 中已加载的 AliyunCaptcha.js;初始化在首次调起时执行。 + */ +export function useAliyunCaptcha() { + /** + * 先弹出滑块,通过后执行 bizVerify(captchaVerifyParam),再根据结果调用 onSuccess。 + * @param { (captchaVerifyParam: string) => Promise<{ success: boolean, data: any, error?: any }> } bizVerify - 业务请求函数,接收滑块参数 + * @param { (res: any) => void } onSuccess - 业务成功回调 + */ + async function runWithCaptcha(bizVerify, onSuccess) { + if (typeof window === "undefined") { + ElMessage.error("验证码仅支持浏览器环境") + return + } + + await loadCaptchaConfig() + + // 后端统一开关关闭时,前端直接跳过滑块校验并继续业务流程 + if (!captchaEnabled) { + const result = await bizVerify(null) + if (typeof onSuccess === "function") { + onSuccess(result) + } + return + } + + const loadingInstance = ElMessage({ + message: "安全验证加载中...", + type: "info", + duration: 0, + iconClass: "el-icon-loading" + }) + + try { + window.__captchaVerifyCallback = async (captchaVerifyParam) => { + window.__lastBizResponse = null + try { + const result = await bizVerify(captchaVerifyParam) + window.__lastBizResponse = result + const captchaOk = result?.data?.captchaVerifyResult !== false + const bizOk = result.success === true + return { captchaResult: captchaOk, bizResult: bizOk } + } catch { + return { captchaResult: false, bizResult: false } + } + } + + window.__onBizResultCallback = (bizResult) => { + if ( + bizResult === true && + window.__lastBizResponse && + typeof window.__onCaptchaBizSuccess === "function" + ) { + window.__onCaptchaBizSuccess(window.__lastBizResponse) + } + } + + await ensureCaptchaInit() + + // 首次初始化时 SDK 会异步调用 getInstance,需等待实例就绪后再 show + if (captchaReadyPromise) { + await captchaReadyPromise + captchaReadyPromise = null + } + if (!window.captcha) { + ElMessage.error("验证码未加载,请刷新页面重试") + return + } + window.__onCaptchaBizSuccess = onSuccess + window.captcha.show() + } finally { + loadingInstance.close() + } + } + + return { runWithCaptcha } +} + +export default useAliyunCaptcha diff --git a/src/composables/useCertification.js b/src/composables/useCertification.js new file mode 100644 index 0000000..d1efa6c --- /dev/null +++ b/src/composables/useCertification.js @@ -0,0 +1,77 @@ +import { isPageRequiresCertification } from '@/constants/menu' +import { useUserStore } from '@/stores/user' +import { computed, onMounted, ref } from 'vue' +import { useRoute } from 'vue-router' + +export function useCertification() { + const route = useRoute() + const userStore = useUserStore() + const certificationLoading = ref(true) + + const requiresCertification = computed(() => { + return isPageRequiresCertification(route.path) + }) + + // 改为 computed,自动响应 userStore.isCertified 的变化 + const isCertified = computed(() => { + return userStore.isCertified || false + }) + + const shouldShowCertificationNotice = computed(() => { + return requiresCertification.value && !isCertified.value && !certificationLoading.value + }) + + const checkCertificationStatus = async () => { + try { + // 这里应该调用实际的API来检查用户认证状态 + // 实际项目中需要替换为真实的API调用 + // 例如:const { data } = await userApi.getCertificationStatus() + + // 模拟API调用延迟 + await new Promise(resolve => setTimeout(resolve, 500)) + } catch (error) { + console.error('Failed to check certification status:', error) + } finally { + certificationLoading.value = false + } + } + + const callProtectedAPI = async (apiMethod, ...args) => { + if (!isCertified.value && requiresCertification.value) { + console.warn('API call blocked: User not certified') + return null + } + + try { + return await apiMethod(...args) + } catch (error) { + if (error.response?.status === 403) { + console.warn('API call failed: Access denied - certification required') + return null + } + throw error + } + } + + const canCallAPI = computed(() => { + return !requiresCertification.value || isCertified.value + }) + + onMounted(() => { + if (requiresCertification.value) { + checkCertificationStatus() + } else { + certificationLoading.value = false + } + }) + + return { + isCertified, + certificationLoading, + requiresCertification, + shouldShowCertificationNotice, + checkCertificationStatus, + callProtectedAPI, + canCallAPI + } +} diff --git a/src/composables/useMobileTable.js b/src/composables/useMobileTable.js new file mode 100644 index 0000000..392630c --- /dev/null +++ b/src/composables/useMobileTable.js @@ -0,0 +1,101 @@ +import { ref, onMounted, onUnmounted, nextTick } from 'vue' + +/** + * 移动端表格优化 composable + * 用于在移动端移除表格固定列,优化显示效果 + */ +export function useMobileTable() { + const isMobile = ref(false) + const isTablet = ref(false) + + // 检测屏幕尺寸 + const checkScreenSize = () => { + if (typeof window === 'undefined') return + const width = window.innerWidth + isMobile.value = width < 768 + isTablet.value = width >= 768 && width < 1024 + } + + // 移除表格固定列 + const removeFixedColumns = () => { + if (typeof window === 'undefined') return + + // 只在移动端执行 + if (!isMobile.value) { + // 桌面端恢复固定列样式 + const tables = document.querySelectorAll('.list-page-container .el-table') + tables.forEach((table) => { + const fixedElements = table.querySelectorAll('.el-table__fixed, .el-table__fixed-right') + fixedElements.forEach((el) => { + el.style.position = '' + el.style.boxShadow = '' + el.style.backgroundColor = '' + }) + }) + return + } + + // 使用 nextTick 确保 DOM 已更新 + nextTick(() => { + setTimeout(() => { + const tables = document.querySelectorAll('.list-page-container .el-table') + tables.forEach((table) => { + // 移除固定列元素 + const fixedElements = table.querySelectorAll('.el-table__fixed, .el-table__fixed-right') + fixedElements.forEach((el) => { + el.style.position = 'static' + el.style.boxShadow = 'none' + el.style.backgroundColor = 'transparent' + el.style.zIndex = 'auto' + }) + + // 移除固定列的表头、表体、表尾包装器 + const fixedWrappers = table.querySelectorAll( + '.el-table__fixed-header-wrapper, .el-table__fixed-body-wrapper, .el-table__fixed-footer-wrapper' + ) + fixedWrappers.forEach((el) => { + el.style.position = 'static' + }) + + // 移除固定列的遮罩层 + const fixedPatch = table.querySelectorAll('.el-table__fixed-right-patch, .el-table__fixed-patch') + fixedPatch.forEach((el) => { + el.style.display = 'none' + }) + }) + }, 150) + }) + } + + // 监听窗口大小变化 + const handleResize = () => { + const wasMobile = isMobile.value + checkScreenSize() + // 如果移动状态发生变化,重新应用优化 + if (wasMobile !== isMobile.value) { + removeFixedColumns() + } + } + + onMounted(() => { + checkScreenSize() + if (typeof window !== 'undefined') { + window.addEventListener('resize', handleResize) + // 初始移除固定列 + removeFixedColumns() + } + }) + + onUnmounted(() => { + if (typeof window !== 'undefined') { + window.removeEventListener('resize', handleResize) + } + }) + + return { + isMobile, + isTablet, + removeFixedColumns + } +} + diff --git a/src/constants/documentationDefaults.js b/src/constants/documentationDefaults.js new file mode 100644 index 0000000..f008b51 --- /dev/null +++ b/src/constants/documentationDefaults.js @@ -0,0 +1,127 @@ +// 产品文档默认内容 +export const DOCUMENTATION_DEFAULTS = { + // 基础说明默认内容 + basicInfo: `## 请求头 + +| 字段名 | 类型 | 必填 | 描述 | +|--------|------|------|------| +| Access-Id | string | 是 | 账号的 Access-Id | + +对于业务请求参数 +通过加密后得到 Base64 字符串,将其放入到请求体中,字段名为 \`data\`,以此方式进行传参。 +\`\`\`json +{ + "data": "xxxx(base64)" +} +\`\`\` + +对接响应得到的公共参数 +\`\`\`json +{ + "code": "int", + "message": "string", + "transaction_id": "string", // 流水号 + "data": "string" +} +\`\`\` +**data** 字段为加密的数据,需要解密后查看。 + +## 加密和解密机制 + +账户获得的密钥(**Access Key**)是一个 16 进制字符串,使用 AES-128 加密算法。 + +### 加密过程: + +- 加密模式:**AES-CBC 模式**。 +- 密钥长度:**128 位(16 字节)**。 +- 填充方式:**PKCS7 填充**。 +- **IV(初始化向量)**:IV 长度为 16 字节(128 位),每次加密时随机生成。 +- 加密后,将 **IV** 和密文拼接在一起进行传输。 +- 最后,将拼接了 IV 的密文通过 **Base64 编码**,方便在网络或文件中传输。 + +### 解密过程: + +- 解密时,首先从 Base64 解码后的数据中提取前 16 字节作为 **IV**。 +- 然后使用提取的 **IV**,通过 AES-CBC 模式解密剩余部分的密文。 +- 解密后去除 **PKCS7 填充**,即可得到原始明文。`, + + // 请求参数默认内容 + requestParams: `## 请求参数 + +\`\`\`json +{ + "mobile_no": "string", + "id_card": "string", + "name": "string" +} +\`\`\` + +| 字段名 | 类型 | 必填 | 描述 | +|--------|------|------|------| +| mobile_no | string | 是 | 手机号 | +| id_card | string | 是 | 身份证号 | +| name | string | 是 | 姓名 | + +通过加密后得到 Base64 字符串,将其放入到请求体中,字段名为 \`data\`。 + +\`\`\`json +{ + "data": "xxxx(base64)" +} +\`\`\``, + + // 返回字段说明默认内容 + responseFields: `## 返回字段说明 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| data.flag_telefraudpredictstd | string | 1(输出成功),0(未匹配上无输出),98(用户输入信息不足),99(系统异常) | +| data.tfps_level | string | 取值 0-6,取值越高,风险越大 |`, + + // 响应示例默认内容 + responseExample: `## 响应示例 + +### 成功响应 + +\`\`\`json +{ + "data": { + "swift_number": "999333_20181029143459_23453A4E0", + "code": "00", + "flag_telefraudpredictstd": "1", + "tfps_level": "1" + } +} +\`\`\``, + + // 错误代码默认内容 + errorCodes: `## 错误代码 + +| code | message | +|------|---------| +| 0 | 业务成功 | +| 1000 | 查询为空 | +| 1001 | 接口异常 | +| 1002 | 参数解密失败 | +| 1003 | 基础参数校验不正确 | +| 1004 | 未经授权的IP | +| 1005 | 缺少Access-Id | +| 1006 | 未经授权的AccessId | +| 1007 | 账户余额不足,无法请求 | +| 1008 | 未开通此产品 | +| 2001 | 业务失败 |` +} + +// 获取默认内容的辅助函数 +export const getDefaultContent = (type) => { + return DOCUMENTATION_DEFAULTS[type] || '' +} + +// 默认内容类型枚举 +export const DEFAULT_CONTENT_TYPES = { + BASIC_INFO: 'basicInfo', + REQUEST_PARAMS: 'requestParams', + RESPONSE_FIELDS: 'responseFields', + RESPONSE_EXAMPLE: 'responseExample', + ERROR_CODES: 'errorCodes' +} diff --git a/src/constants/icons.js b/src/constants/icons.js new file mode 100644 index 0000000..4cac49f --- /dev/null +++ b/src/constants/icons.js @@ -0,0 +1,80 @@ +// 图标映射配置 - Element Plus 到 Heroicons 的映射 +import { + ArrowRightOnRectangleIcon, + Bars3Icon, + BellIcon, + CalendarIcon, + CheckCircleIcon, + CheckIcon, + ChevronDownIcon, + ClockIcon, + Cog6ToothIcon, + DocumentCheckIcon, + ExclamationCircleIcon, + ExclamationTriangleIcon, + EyeIcon, + EyeSlashIcon, + FunnelIcon, + // 基础图标 + HomeIcon, + InformationCircleIcon, + LinkIcon, + MagnifyingGlassIcon, + MinusIcon, + PencilIcon, + // 其他常用图标 + PlusIcon, + ShieldCheckIcon, + TrashIcon, + UserCircleIcon, + UserIcon, + // 管理相关 + UsersIcon, + WalletIcon, + XMarkIcon +} from '@heroicons/vue/24/outline' + +// Element Plus 图标到 Heroicons 的映射 +export const iconMap = { + // 基础导航图标 + House: HomeIcon, + User: UserIcon, + Setting: Cog6ToothIcon, + Shield: ShieldCheckIcon, + Wallet: WalletIcon, + Link: LinkIcon, + Bell: BellIcon, + Menu: Bars3Icon, + ArrowDown: ChevronDownIcon, + Close: XMarkIcon, + Info: InformationCircleIcon, + UserFilled: UserCircleIcon, + Switch: ArrowRightOnRectangleIcon, + + // 管理相关图标 + UserFilled: UsersIcon, + DocumentCheck: DocumentCheckIcon, + + // 其他常用图标 + Plus: PlusIcon, + Minus: MinusIcon, + Edit: PencilIcon, + Delete: TrashIcon, + View: EyeIcon, + Hide: EyeSlashIcon, + Search: MagnifyingGlassIcon, + Filter: FunnelIcon, + Calendar: CalendarIcon, + Clock: ClockIcon, + Check: CheckIcon, + Warning: ExclamationTriangleIcon, + Error: ExclamationCircleIcon, + Success: CheckCircleIcon +} + +// 导出所有 Heroicons 图标供直接使用 +export { + ArrowRightOnRectangleIcon, Bars3Icon, BellIcon, CalendarIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, ClockIcon, Cog6ToothIcon, DocumentCheckIcon, ExclamationCircleIcon, ExclamationTriangleIcon, EyeIcon, + EyeSlashIcon, FunnelIcon, HomeIcon, InformationCircleIcon, LinkIcon, MagnifyingGlassIcon, MinusIcon, + PencilIcon, PlusIcon, ShieldCheckIcon, TrashIcon, UserCircleIcon, UserIcon, UsersIcon, WalletIcon, XMarkIcon +} diff --git a/src/constants/index.js b/src/constants/index.js new file mode 100644 index 0000000..3fd3894 --- /dev/null +++ b/src/constants/index.js @@ -0,0 +1,225 @@ +// 应用常量 +export const APP_NAME = '海宇数据控制台' +export const APP_VERSION = '1.0.0' + +// API相关常量 +export const API_BASE_URL = '/api/v1' +export const API_TIMEOUT = 10000 + +// 认证相关常量 +export const TOKEN_KEY = 'token' +export const REFRESH_TOKEN_KEY = 'refresh_token' + +// 用户角色 +export const USER_ROLES = { + USER: 'user', + ADMIN: 'admin', + SUPER_ADMIN: 'super_admin' +} + +// 认证状态 +export const CERTIFICATION_STATUS = { + PENDING: 'pending', + SUBMITTED: 'submitted', + REVIEWING: 'reviewing', + APPROVED: 'approved', + REJECTED: 'rejected' +} + +// 认证状态中文映射 +export const CERTIFICATION_STATUS_TEXT = { + [CERTIFICATION_STATUS.PENDING]: '待提交', + [CERTIFICATION_STATUS.SUBMITTED]: '已提交', + [CERTIFICATION_STATUS.REVIEWING]: '审核中', + [CERTIFICATION_STATUS.APPROVED]: '已通过', + [CERTIFICATION_STATUS.REJECTED]: '已拒绝' +} + +// 交易类型 +export const TRANSACTION_TYPES = { + RECHARGE: 'recharge', + WITHDRAW: 'withdraw', + CONSUME: 'consume', + REFUND: 'refund' +} + +// 交易状态 +export const TRANSACTION_STATUS = { + PENDING: 'pending', + PROCESSING: 'processing', + SUCCESS: 'success', + FAILED: 'failed', + CANCELLED: 'cancelled' +} + +// 交易状态中文映射 +export const TRANSACTION_STATUS_TEXT = { + [TRANSACTION_STATUS.PENDING]: '待处理', + [TRANSACTION_STATUS.PROCESSING]: '处理中', + [TRANSACTION_STATUS.SUCCESS]: '成功', + [TRANSACTION_STATUS.FAILED]: '失败', + [TRANSACTION_STATUS.CANCELLED]: '已取消' +} + +// 验证码场景 +export const SMS_SCENES = { + REGISTER: 'register', + LOGIN: 'login', + CHANGE_PASSWORD: 'change_password', + RESET_PASSWORD: 'reset_password', + BIND: 'bind', + UNBIND: 'unbind' +} + +// 验证码场景中文映射 +export const SMS_SCENES_TEXT = { + [SMS_SCENES.REGISTER]: '注册', + [SMS_SCENES.LOGIN]: '登录', + [SMS_SCENES.CHANGE_PASSWORD]: '修改密码', + [SMS_SCENES.RESET_PASSWORD]: '重置密码', + [SMS_SCENES.BIND]: '绑定', + [SMS_SCENES.UNBIND]: '解绑' +} + +// 文件上传相关 +export const UPLOAD_CONFIG = { + MAX_SIZE: 10 * 1024 * 1024, // 10MB + ALLOWED_TYPES: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'], + ALLOWED_EXTENSIONS: ['.jpg', '.jpeg', '.png', '.gif', '.pdf'] +} + +// 分页配置 +export const PAGINATION_CONFIG = { + DEFAULT_PAGE_SIZE: 10, + PAGE_SIZE_OPTIONS: [10, 20, 50, 100] +} + +// 主题配置 +export const THEME_CONFIG = { + LIGHT: 'light', + DARK: 'dark' +} + +// 语言配置 +export const LANGUAGE_CONFIG = { + ZH_CN: 'zh-CN', + EN_US: 'en-US' +} + +// 路由配置 +export const ROUTE_CONFIG = { + AUTH: { + LOGIN: '/auth/login', + REGISTER: '/auth/register', + RESET_PASSWORD: '/auth/reset' + }, + DASHBOARD: '/products', + CERTIFICATION: '/certification', + FINANCE: '/finance', + API: '/api', + PROFILE: '/profile', + ADMIN: '/admin' +} + +// 权限配置 +export const PERMISSIONS = { + // 用户权限 + USER_READ: 'user:read', + USER_WRITE: 'user:write', + USER_DELETE: 'user:delete', + + // 认证权限 + CERTIFICATION_READ: 'certification:read', + CERTIFICATION_WRITE: 'certification:write', + CERTIFICATION_REVIEW: 'certification:review', + + // 财务权限 + FINANCE_READ: 'finance:read', + FINANCE_WRITE: 'finance:write', + FINANCE_APPROVE: 'finance:approve', + + // API权限 + API_READ: 'api:read', + API_WRITE: 'api:write', + API_DELETE: 'api:delete', + + // 系统权限 + SYSTEM_READ: 'system:read', + SYSTEM_WRITE: 'system:write', + SYSTEM_ADMIN: 'system:admin' +} + +// 错误码配置 +export const ERROR_CODES = { + // 通用错误 + UNKNOWN_ERROR: 'UNKNOWN_ERROR', + NETWORK_ERROR: 'NETWORK_ERROR', + TIMEOUT_ERROR: 'TIMEOUT_ERROR', + + // 认证错误 + UNAUTHORIZED: 'UNAUTHORIZED', + FORBIDDEN: 'FORBIDDEN', + TOKEN_EXPIRED: 'TOKEN_EXPIRED', + INVALID_CREDENTIALS: 'INVALID_CREDENTIALS', + + // 业务错误 + VALIDATION_ERROR: 'VALIDATION_ERROR', + RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND', + RESOURCE_ALREADY_EXISTS: 'RESOURCE_ALREADY_EXISTS', + OPERATION_FAILED: 'OPERATION_FAILED', + + // 用户相关错误 + USER_NOT_FOUND: 'USER_NOT_FOUND', + USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS', + INVALID_PHONE: 'INVALID_PHONE', + INVALID_CODE: 'INVALID_CODE', + CODE_EXPIRED: 'CODE_EXPIRED', + + // 认证相关错误 + CERTIFICATION_NOT_FOUND: 'CERTIFICATION_NOT_FOUND', + CERTIFICATION_ALREADY_EXISTS: 'CERTIFICATION_ALREADY_EXISTS', + CERTIFICATION_IN_PROGRESS: 'CERTIFICATION_IN_PROGRESS', + + // 财务相关错误 + INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE', + TRANSACTION_FAILED: 'TRANSACTION_FAILED', + WALLET_NOT_FOUND: 'WALLET_NOT_FOUND' +} + +// 本地存储键名 +export const STORAGE_KEYS = { + TOKEN: 'token', + USER_INFO: 'user_info', + THEME: 'theme', + LANGUAGE: 'language', + SIDEBAR_COLLAPSED: 'sidebar_collapsed', + NOTIFICATIONS: 'notifications' +} + +// 正则表达式 +export const REGEX = { + PHONE: /^1[3-9]\d{9}$/, + EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + ID_CARD: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, + PASSWORD: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/, + VERIFY_CODE: /^\d{6}$/ +} + +// 时间格式 +export const DATE_FORMATS = { + DATE: 'YYYY-MM-DD', + DATETIME: 'YYYY-MM-DD HH:mm:ss', + TIME: 'HH:mm:ss', + MONTH: 'YYYY-MM', + YEAR: 'YYYY' +} + +// 响应式断点 +export const BREAKPOINTS = { + XS: 480, + SM: 768, + MD: 1024, + LG: 1280, + XL: 1440, + XXL: 1920 +} diff --git a/src/constants/menu.js b/src/constants/menu.js new file mode 100644 index 0000000..0401563 --- /dev/null +++ b/src/constants/menu.js @@ -0,0 +1,195 @@ +import { + ChartBarIcon as ChartBar, + ChartPieIcon as ChartPie, + ClipboardDocumentListIcon as Clipboard, + CreditCardIcon as CreditCard, + CubeIcon as Cube, + DocumentTextIcon as DocumentText, + MegaphoneIcon as Megaphone, + PresentationChartLineIcon as PresentationChartLine, + Cog6ToothIcon as Setting, + ShieldCheckIcon as ShieldCheck, + ShoppingCartIcon as ShoppingCart, + TagIcon as Tag, + UserIcon as User, + UserGroupIcon as Users, + WalletIcon as Wallet +} from '@heroicons/vue/24/outline' + +// 用户菜单配置(分组结构) +export const userMenuItems = [ + { + group: '数据中心', + icon: ChartBar, + children: [ + { name: '仪表盘', path: '/dashboard', icon: PresentationChartLine }, + { name: '数据大厅', path: '/products', icon: Cube }, + { name: '我的订阅', path: '/subscriptions', icon: ShoppingCart } + ] + }, + { + group: '账户中心', + icon: User, + children: [ + { name: '账户中心', path: '/profile', icon: User }, + { name: '企业入驻', path: '/profile/certification', icon: ShieldCheck } + ] + }, + { + group: '财务管理', + icon: Wallet, + children: [ + { name: '余额充值', path: '/finance/wallet', icon: CreditCard, requiresCertification: true }, + { name: '充值记录', path: '/finance/recharge-records', icon: CreditCard, requiresCertification: true }, + { name: '购买记录', path: '/finance/purchase-records', icon: ShoppingCart, requiresCertification: true }, + { name: '消费记录', path: '/finance/transactions', icon: Clipboard, requiresCertification: true }, + { name: '发票申请', path: '/finance/invoice', icon: Wallet, requiresCertification: true } + ] + }, + { + group: '开发者中心', + icon: Setting, + children: [ + // { name: 'API管理', path: '/api/management', icon: Key }, + { name: '在线调试', path: '/apis/debugger', icon: Clipboard, requiresCertification: true }, + { name: '调用记录', path: '/apis/usage', icon: Clipboard, requiresCertification: true }, + { name: '白名单管理', path: '/apis/whitelist', icon: ShieldCheck, requiresCertification: true } + ] + } +] + +// 管理员菜单配置 +export const adminMenuItems = [ + { + name: '产品管理', + path: '/admin/products', + icon: Cube + }, + { + name: '分类管理', + path: '/admin/categories', + icon: Tag + }, + { + name: '订阅管理', + path: '/admin/subscriptions', + icon: ShoppingCart + }, + { + name: '用户管理', + path: '/admin/users', + icon: Users + }, + { + name: '文章管理', + path: '/admin/articles', + icon: DocumentText + }, + { + name: '公告管理', + path: '/admin/announcements', + icon: Megaphone + }, + { + name: '系统统计', + path: '/admin/statistics', + icon: ChartPie + } +] + +// 新增:根据用户类型动态生成菜单 +export const getMenuItems = (userType = 'user') => { + if (userType === 'admin') { + return adminMenuItems + } + return userMenuItems +} + +// 新增:获取用户可访问的菜单项(包含管理员菜单) +export const getUserAccessibleMenuItems = (userType = 'user') => { + const baseMenuItems = [...userMenuItems] + + // 如果是管理员,添加管理员菜单组 + if (userType === 'admin') { + baseMenuItems.push({ + group: '管理后台', + icon: Setting, + children: [ + { name: '系统统计', path: '/admin/statistics', icon: ChartBar }, + { name: '企业审核', path: '/admin/certification-reviews', icon: ShieldCheck }, + { name: '产品管理', path: '/admin/products', icon: Cube }, + { name: '用户管理', path: '/admin/users', icon: Users }, + { name: '分类管理', path: '/admin/categories', icon: Tag }, + { name: '订阅管理', path: '/admin/subscriptions', icon: ShoppingCart }, + { name: '文章管理', path: '/admin/articles', icon: DocumentText }, + { name: '公告管理', path: '/admin/announcements', icon: Megaphone }, + { name: '调用记录', path: '/admin/usage', icon: Clipboard }, + { name: '消费记录', path: '/admin/transactions', icon: Clipboard }, + { name: '充值记录', path: '/admin/recharge-records', icon: CreditCard }, + { name: '购买记录', path: '/admin/purchase-records', icon: ShoppingCart }, + { name: '发票管理', path: '/admin/invoices', icon: Wallet }, + { name: '组件管理', path: '/admin/ui-components', icon: Cube } + ] + }) + } + + return baseMenuItems +} + +// 需要企业认证的页面路径列表 +export const requiresCertificationPaths = [ + '/finance/wallet', + '/finance/recharge-records', + '/finance/purchase-records', + '/finance/transactions', + '/apis/usage', + '/apis/whitelist' +] + +// 检查页面是否需要企业认证 +export const isPageRequiresCertification = (path) => { + return requiresCertificationPaths.includes(path) +} + +// 获取当前页面的认证配置 +export const getCurrentPageCertificationConfig = (path) => { + // 检查是否在需要认证的路径列表中 + const requiresCert = isPageRequiresCertification(path) + + if (!requiresCert) { + return null + } + + // 根据路径获取页面信息 + const pageConfig = { + '/finance/wallet': { + title: '钱包充值', + description: '为了享受完整的充值服务,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' + }, + '/finance/recharge-records': { + title: '充值记录', + description: '为了查看完整的充值记录,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' + }, + '/finance/purchase-records': { + title: '购买记录', + description: '为了查看完整的购买记录,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' + }, + '/finance/transactions': { + title: '消费记录', + description: '为了查看完整的消费记录,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' + }, + '/apis/usage': { + title: 'API调用记录', + description: '为了查看完整的API调用记录,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' + }, + '/apis/whitelist': { + title: '白名单管理', + description: '为了管理API访问白名单,请先完成企业入驻认证。认证成功后我们将赠送您一定的调用额度!' + } + } + + return { + requiresCertification: true, + ...pageConfig[path] + } +} diff --git a/src/layouts/AuthLayout.vue b/src/layouts/AuthLayout.vue new file mode 100644 index 0000000..8e52ce0 --- /dev/null +++ b/src/layouts/AuthLayout.vue @@ -0,0 +1,296 @@ + + + + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue new file mode 100644 index 0000000..c721f22 --- /dev/null +++ b/src/layouts/MainLayout.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..f07549b --- /dev/null +++ b/src/main.js @@ -0,0 +1,53 @@ +import './assets/main.css' + +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import { createPinia } from 'pinia' +import { createApp } from 'vue' + +import App from './App.vue' +import router from './router' + +// 导入全局样式 +import './assets/styles/index.css' + +// 导入渲染性能优化工具 +import { initRenderOptimizations } from './utils/performance.js' + +const app = createApp(App) + +// 初始化渲染性能优化 +initRenderOptimizations() + +// 全局错误捕获 +app.config.errorHandler = (err, vm, info) => { + console.error('Vue错误:', err) + console.error('错误信息:', info) + console.error('组件:', vm) + console.error('错误堆栈:', err.stack) +} + +// 全局未捕获异常处理 +window.addEventListener('error', (event) => { + console.error('全局错误:', event.error) + console.error('错误消息:', event.message) + console.error('错误文件:', event.filename) + console.error('错误行号:', event.lineno) + console.error('错误列号:', event.colno) + console.error('错误堆栈:', event.error?.stack) +}) + +// Promise未捕获异常处理 +window.addEventListener('unhandledrejection', (event) => { + console.error('Promise错误:', event.reason) + console.error('Promise堆栈:', event.reason?.stack) +}) + +app.use(createPinia()) +app.use(router) +app.use(ElementPlus, { + locale: zhCn, +}) + +app.mount('#app') diff --git a/src/mixins/certificationMixin.js b/src/mixins/certificationMixin.js new file mode 100644 index 0000000..d8a366e --- /dev/null +++ b/src/mixins/certificationMixin.js @@ -0,0 +1,73 @@ +import CertificationNotice from '@/components/common/CertificationNotice.vue' +import { isPageRequiresCertification } from '@/constants/menu' + +export default { + components: { + CertificationNotice + }, + + data() { + return { + isCertified: false, + certificationLoading: true + } + }, + + computed: { + requiresCertification() { + return isPageRequiresCertification(this.$route.path) + }, + + shouldShowCertificationNotice() { + return this.requiresCertification && !this.isCertified && !this.certificationLoading + } + }, + + async created() { + if (this.requiresCertification) { + await this.checkCertificationStatus() + } + }, + + methods: { + async checkCertificationStatus() { + try { + // 这里应该调用实际的API来检查用户认证状态 + // 实际项目中需要替换为真实的API调用 + const userStore = this.$store?.state?.user + this.isCertified = userStore?.isCertified || false + + // 模拟API调用延迟 + await new Promise(resolve => setTimeout(resolve, 500)) + } catch (error) { + console.error('Failed to check certification status:', error) + this.isCertified = false + } finally { + this.certificationLoading = false + } + }, + + // 安全调用需要认证的API + async callProtectedAPI(apiMethod, ...args) { + if (!this.isCertified && this.requiresCertification) { + console.warn('API call blocked: User not certified') + return null + } + + try { + return await apiMethod(...args) + } catch (error) { + if (error.response?.status === 403) { + console.warn('API call failed: Access denied - certification required') + return null + } + throw error + } + }, + + // 检查是否可以进行API调用 + canCallAPI() { + return !this.requiresCertification || this.isCertified + } + } +} diff --git a/src/pages/FileUploadTest.vue b/src/pages/FileUploadTest.vue new file mode 100644 index 0000000..403931e --- /dev/null +++ b/src/pages/FileUploadTest.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/pages/StatisticsTest.vue b/src/pages/StatisticsTest.vue new file mode 100644 index 0000000..797d065 --- /dev/null +++ b/src/pages/StatisticsTest.vue @@ -0,0 +1,334 @@ + + + + + diff --git a/src/pages/admin/announcements/components/AnnouncementDetailDialog.vue b/src/pages/admin/announcements/components/AnnouncementDetailDialog.vue new file mode 100644 index 0000000..e7b9a0f --- /dev/null +++ b/src/pages/admin/announcements/components/AnnouncementDetailDialog.vue @@ -0,0 +1,167 @@ + + + + + + diff --git a/src/pages/admin/announcements/components/AnnouncementEditDialog.vue b/src/pages/admin/announcements/components/AnnouncementEditDialog.vue new file mode 100644 index 0000000..44c3602 --- /dev/null +++ b/src/pages/admin/announcements/components/AnnouncementEditDialog.vue @@ -0,0 +1,306 @@ + + + + + + diff --git a/src/pages/admin/announcements/components/AnnouncementStats.vue b/src/pages/admin/announcements/components/AnnouncementStats.vue new file mode 100644 index 0000000..d27f92f --- /dev/null +++ b/src/pages/admin/announcements/components/AnnouncementStats.vue @@ -0,0 +1,85 @@ + + + + diff --git a/src/pages/admin/announcements/components/SchedulePublishDialog.vue b/src/pages/admin/announcements/components/SchedulePublishDialog.vue new file mode 100644 index 0000000..78eeb29 --- /dev/null +++ b/src/pages/admin/announcements/components/SchedulePublishDialog.vue @@ -0,0 +1,186 @@ + + + + diff --git a/src/pages/admin/announcements/index.vue b/src/pages/admin/announcements/index.vue new file mode 100644 index 0000000..6cb5664 --- /dev/null +++ b/src/pages/admin/announcements/index.vue @@ -0,0 +1,870 @@ + + + + + + diff --git a/src/pages/admin/api-calls/index.vue b/src/pages/admin/api-calls/index.vue new file mode 100644 index 0000000..7e88fc6 --- /dev/null +++ b/src/pages/admin/api-calls/index.vue @@ -0,0 +1,1195 @@ + + + + + diff --git a/src/pages/admin/articles/categories.vue b/src/pages/admin/articles/categories.vue new file mode 100644 index 0000000..11c260c --- /dev/null +++ b/src/pages/admin/articles/categories.vue @@ -0,0 +1,175 @@ + + + diff --git a/src/pages/admin/articles/components/ArticleDetailDialog.vue b/src/pages/admin/articles/components/ArticleDetailDialog.vue new file mode 100644 index 0000000..1f48bee --- /dev/null +++ b/src/pages/admin/articles/components/ArticleDetailDialog.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/src/pages/admin/articles/components/ArticleEditDialog.vue b/src/pages/admin/articles/components/ArticleEditDialog.vue new file mode 100644 index 0000000..a9593ac --- /dev/null +++ b/src/pages/admin/articles/components/ArticleEditDialog.vue @@ -0,0 +1,339 @@ + + + diff --git a/src/pages/admin/articles/components/ArticleStats.vue b/src/pages/admin/articles/components/ArticleStats.vue new file mode 100644 index 0000000..2bc31bc --- /dev/null +++ b/src/pages/admin/articles/components/ArticleStats.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/pages/admin/articles/components/CategoryEditDialog.vue b/src/pages/admin/articles/components/CategoryEditDialog.vue new file mode 100644 index 0000000..5d25061 --- /dev/null +++ b/src/pages/admin/articles/components/CategoryEditDialog.vue @@ -0,0 +1,160 @@ + + + diff --git a/src/pages/admin/articles/components/SchedulePublishDialog.vue b/src/pages/admin/articles/components/SchedulePublishDialog.vue new file mode 100644 index 0000000..6f6eb21 --- /dev/null +++ b/src/pages/admin/articles/components/SchedulePublishDialog.vue @@ -0,0 +1,206 @@ + + + diff --git a/src/pages/admin/articles/components/TagEditDialog.vue b/src/pages/admin/articles/components/TagEditDialog.vue new file mode 100644 index 0000000..6024e41 --- /dev/null +++ b/src/pages/admin/articles/components/TagEditDialog.vue @@ -0,0 +1,178 @@ + + + diff --git a/src/pages/admin/articles/index.vue b/src/pages/admin/articles/index.vue new file mode 100644 index 0000000..b3a2ad8 --- /dev/null +++ b/src/pages/admin/articles/index.vue @@ -0,0 +1,988 @@ + + + + + diff --git a/src/pages/admin/articles/tags.vue b/src/pages/admin/articles/tags.vue new file mode 100644 index 0000000..1d11fa9 --- /dev/null +++ b/src/pages/admin/articles/tags.vue @@ -0,0 +1,187 @@ + + + diff --git a/src/pages/admin/categories/index.vue b/src/pages/admin/categories/index.vue new file mode 100644 index 0000000..05cd76e --- /dev/null +++ b/src/pages/admin/categories/index.vue @@ -0,0 +1,617 @@ + + + + + + diff --git a/src/pages/admin/certification-reviews/index.vue b/src/pages/admin/certification-reviews/index.vue new file mode 100644 index 0000000..bfba7f4 --- /dev/null +++ b/src/pages/admin/certification-reviews/index.vue @@ -0,0 +1,758 @@ + + + + + diff --git a/src/pages/admin/consumption/index.vue b/src/pages/admin/consumption/index.vue new file mode 100644 index 0000000..451736b --- /dev/null +++ b/src/pages/admin/consumption/index.vue @@ -0,0 +1,529 @@ + + + + + diff --git a/src/pages/admin/invoices/index.vue b/src/pages/admin/invoices/index.vue new file mode 100644 index 0000000..7707d3a --- /dev/null +++ b/src/pages/admin/invoices/index.vue @@ -0,0 +1,949 @@ + + + + + diff --git a/src/pages/admin/products/index.vue b/src/pages/admin/products/index.vue new file mode 100644 index 0000000..15d36f6 --- /dev/null +++ b/src/pages/admin/products/index.vue @@ -0,0 +1,734 @@ + + + + + diff --git a/src/pages/admin/purchase-records/index.vue b/src/pages/admin/purchase-records/index.vue new file mode 100644 index 0000000..1007176 --- /dev/null +++ b/src/pages/admin/purchase-records/index.vue @@ -0,0 +1,857 @@ + + + + + diff --git a/src/pages/admin/recharge-records/index.vue b/src/pages/admin/recharge-records/index.vue new file mode 100644 index 0000000..8c5aa94 --- /dev/null +++ b/src/pages/admin/recharge-records/index.vue @@ -0,0 +1,811 @@ + + + + + diff --git a/src/pages/admin/statistics/AdminStatisticsPage.vue b/src/pages/admin/statistics/AdminStatisticsPage.vue new file mode 100644 index 0000000..bcc5754 --- /dev/null +++ b/src/pages/admin/statistics/AdminStatisticsPage.vue @@ -0,0 +1,551 @@ + + + + + diff --git a/src/pages/admin/statistics/DashboardManagement.vue b/src/pages/admin/statistics/DashboardManagement.vue new file mode 100644 index 0000000..fd5e946 --- /dev/null +++ b/src/pages/admin/statistics/DashboardManagement.vue @@ -0,0 +1,591 @@ + + + + + diff --git a/src/pages/admin/statistics/MetricsManagement.vue b/src/pages/admin/statistics/MetricsManagement.vue new file mode 100644 index 0000000..92ca72e --- /dev/null +++ b/src/pages/admin/statistics/MetricsManagement.vue @@ -0,0 +1,558 @@ + + + + + diff --git a/src/pages/admin/statistics/ReportsManagement.vue b/src/pages/admin/statistics/ReportsManagement.vue new file mode 100644 index 0000000..a0ece44 --- /dev/null +++ b/src/pages/admin/statistics/ReportsManagement.vue @@ -0,0 +1,660 @@ + + + + + diff --git a/src/pages/admin/statistics/StatisticsSettings.vue b/src/pages/admin/statistics/StatisticsSettings.vue new file mode 100644 index 0000000..e0ede6f --- /dev/null +++ b/src/pages/admin/statistics/StatisticsSettings.vue @@ -0,0 +1,413 @@ + + + + + diff --git a/src/pages/admin/statistics/SystemStatisticsPage.vue b/src/pages/admin/statistics/SystemStatisticsPage.vue new file mode 100644 index 0000000..0ba9c26 --- /dev/null +++ b/src/pages/admin/statistics/SystemStatisticsPage.vue @@ -0,0 +1,1359 @@ + + + + + diff --git a/src/pages/admin/statistics/components/RequestFlowGlobe.vue b/src/pages/admin/statistics/components/RequestFlowGlobe.vue new file mode 100644 index 0000000..9e33de8 --- /dev/null +++ b/src/pages/admin/statistics/components/RequestFlowGlobe.vue @@ -0,0 +1,337 @@ + + + + + + diff --git a/src/pages/admin/subscriptions/index.vue b/src/pages/admin/subscriptions/index.vue new file mode 100644 index 0000000..5f01aaf --- /dev/null +++ b/src/pages/admin/subscriptions/index.vue @@ -0,0 +1,1475 @@ + + + + + diff --git a/src/pages/admin/transactions/index.vue b/src/pages/admin/transactions/index.vue new file mode 100644 index 0000000..75e67a6 --- /dev/null +++ b/src/pages/admin/transactions/index.vue @@ -0,0 +1,884 @@ + + + + + diff --git a/src/pages/admin/ui-components/index.vue b/src/pages/admin/ui-components/index.vue new file mode 100644 index 0000000..b0dd3f8 --- /dev/null +++ b/src/pages/admin/ui-components/index.vue @@ -0,0 +1,1231 @@ + + + + + \ No newline at end of file diff --git a/src/pages/admin/usage/index.vue b/src/pages/admin/usage/index.vue new file mode 100644 index 0000000..431ef1f --- /dev/null +++ b/src/pages/admin/usage/index.vue @@ -0,0 +1,784 @@ + + + + + diff --git a/src/pages/admin/users/index.vue b/src/pages/admin/users/index.vue new file mode 100644 index 0000000..9b3d63c --- /dev/null +++ b/src/pages/admin/users/index.vue @@ -0,0 +1,1540 @@ + + + + + diff --git a/src/pages/api/ApiDebugger.vue b/src/pages/api/ApiDebugger.vue new file mode 100644 index 0000000..8117f39 --- /dev/null +++ b/src/pages/api/ApiDebugger.vue @@ -0,0 +1,1527 @@ + + + + + diff --git a/src/pages/api/ApiManagement.vue b/src/pages/api/ApiManagement.vue new file mode 100644 index 0000000..3ad7cd4 --- /dev/null +++ b/src/pages/api/ApiManagement.vue @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/pages/api/Usage.vue b/src/pages/api/Usage.vue new file mode 100644 index 0000000..f25e72b --- /dev/null +++ b/src/pages/api/Usage.vue @@ -0,0 +1,837 @@ + + + + + diff --git a/src/pages/api/WhiteList.vue b/src/pages/api/WhiteList.vue new file mode 100644 index 0000000..5ab625f --- /dev/null +++ b/src/pages/api/WhiteList.vue @@ -0,0 +1,665 @@ + + + + + diff --git a/src/pages/auth/AuthIntegrationTest.vue b/src/pages/auth/AuthIntegrationTest.vue new file mode 100644 index 0000000..062d471 --- /dev/null +++ b/src/pages/auth/AuthIntegrationTest.vue @@ -0,0 +1,193 @@ + + + diff --git a/src/pages/auth/Login.vue b/src/pages/auth/Login.vue new file mode 100644 index 0000000..ae8490c --- /dev/null +++ b/src/pages/auth/Login.vue @@ -0,0 +1,459 @@ + + + + + diff --git a/src/pages/auth/Register.vue b/src/pages/auth/Register.vue new file mode 100644 index 0000000..b80ca3a --- /dev/null +++ b/src/pages/auth/Register.vue @@ -0,0 +1,440 @@ + + + + + diff --git a/src/pages/auth/ResetPassword.vue b/src/pages/auth/ResetPassword.vue new file mode 100644 index 0000000..285c348 --- /dev/null +++ b/src/pages/auth/ResetPassword.vue @@ -0,0 +1,341 @@ + + + + + diff --git a/src/pages/auth/ResponseTest.vue b/src/pages/auth/ResponseTest.vue new file mode 100644 index 0000000..1a2b432 --- /dev/null +++ b/src/pages/auth/ResponseTest.vue @@ -0,0 +1,173 @@ + + + diff --git a/src/pages/auth/TestStore.vue b/src/pages/auth/TestStore.vue new file mode 100644 index 0000000..92a184f --- /dev/null +++ b/src/pages/auth/TestStore.vue @@ -0,0 +1,162 @@ + + + diff --git a/src/pages/certification/IframeCallback.vue b/src/pages/certification/IframeCallback.vue new file mode 100644 index 0000000..43d0d2f --- /dev/null +++ b/src/pages/certification/IframeCallback.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/pages/certification/README.md b/src/pages/certification/README.md new file mode 100644 index 0000000..0c3cc1c --- /dev/null +++ b/src/pages/certification/README.md @@ -0,0 +1,333 @@ +# 企业入驻流程 + +## 📋 概述 + +企业入驻流程是一个基于Vue 3 + Element Plus + Tailwind CSS构建的步骤式认证系统,用于引导用户完成企业信息填写、认证验证、合同签署等入驻流程。 + +## 🏗️ 架构设计 + +### 目录结构 +``` +certification/ +├── index.vue # 主页面容器 +├── components/ # 子组件目录 +│ ├── EnterpriseInfo.vue # 企业信息填写 +│ ├── EnterpriseVerify.vue # 企业认证验证 +│ ├── ContractSign.vue # 合同签署 +│ └── CertificationComplete.vue # 完成页面 +└── README.md # 本文档 +``` + +### 技术栈 +- **前端框架**: Vue 3 (Composition API) +- **UI组件库**: Element Plus +- **样式框架**: Tailwind CSS v4 +- **图标库**: Heroicons +- **状态管理**: Pinia (用户状态) +- **路由**: Vue Router + +## 🔄 流程步骤 + +### 1. 填写企业信息 (`enterprise_info`) +**组件**: `EnterpriseInfo.vue` + +**功能特性**: +- 企业基本信息收集 +- 法人信息验证 +- 手机号验证码验证 +- 实时表单验证 + +**表单字段**: +- 企业名称 (2-100字符) +- 统一社会信用代码 (18位) +- 法人姓名 (2-20字符) +- 法人身份证号 (18位) +- 法人手机号 (11位) +- 验证码 (6位数字) + +**验证规则**: +```javascript +// 统一社会信用代码验证 +const pattern = /^[0-9A-HJ-NPQRTUWXY]{18}$/ + +// 身份证号验证 +const pattern = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/ + +// 手机号验证 +const pattern = /^1[3-9]\d{9}$/ +``` + +### 2. 企业认证 (`enterprise_verify`) +**组件**: `EnterpriseVerify.vue` + +**功能特性**: +- 集成e签宝企业认证服务 +- iframe嵌入认证页面 +- 认证状态监控 +- 加载状态管理 + +**集成服务**: +- 认证URL: `https://smlt.esign.cn/Z1Dc9Ts` +- 支持iframe加载 +- 认证完成回调处理 + +### 3. 签署合同 (`contract_sign`) +**组件**: `ContractSign.vue` + +**功能特性**: +- 集成电子合同签署服务 +- iframe嵌入签署页面 +- 签署状态监控 +- 完成确认机制 + +**集成服务**: +- 签署URL: `https://smlt.esign.cn/95wqyN2` +- 支持iframe加载 +- 签署完成回调处理 + +### 4. 完成入驻 (`completed`) +**组件**: `CertificationComplete.vue` + +**功能特性**: +- 入驻成功展示 +- 企业信息汇总 +- 后续操作引导 +- 状态标签显示 + +**操作入口**: +- 前往控制台 +- 返回账户中心 + +## 🎨 UI/UX 设计 + +### 视觉设计原则 +- **现代化**: 毛玻璃效果、渐变背景、圆角设计 +- **一致性**: 统一的色彩系统和间距规范 +- **可访问性**: 清晰的视觉层次和状态反馈 + +### 响应式设计 +```css +/* 桌面端 */ +@media (min-width: 1200px) { + .iframe-container { height: 700px; } +} + +/* 移动端 */ +@media (max-width: 768px) { + .iframe-container { height: 400px; } + .form-section { padding: 20px; } +} +``` + +### 状态指示 +- **未开始**: 灰色图标,等待状态 +- **进行中**: 蓝色图标,脉冲动画 +- **已完成**: 绿色图标,完成状态 + +## 🔧 技术实现 + +### 状态管理 +```javascript +// 主要状态 +const currentStep = ref('enterprise_info') +const certificationData = ref(null) +const enterpriseForm = ref({ + companyName: '', + unifiedSocialCode: '', + legalPersonName: '', + legalPersonID: '', + legalPersonPhone: '', + legalPersonCode: '' +}) +``` + +### 组件通信 +```javascript +// 事件定义 +const emit = defineEmits(['submit', 'apply', 'check-status', 'go-dashboard', 'go-profile']) + +// 数据传递 +const props = defineProps({ + formData: Object, + enterpriseData: Object, + companyName: String, + certificationData: Object +}) +``` + +### 表单验证 +```javascript +// 自定义验证器 +const validateUnifiedSocialCode = (rule, value, callback) => { + const pattern = /^[0-9A-HJ-NPQRTUWXY]{18}$/ + if (!pattern.test(value)) { + callback(new Error('统一社会信用代码格式不正确')) + return + } + callback() +} +``` + +## 🚀 开发特性 + +### 开发模式 +```javascript +// 开发模式控制器 +const isDevelopment = ref(false) +const devCurrentStep = ref('enterprise_info') + +// 步骤切换功能 +const switchToStep = (stepKey) => { + currentStep.value = stepKey +} + +const resetToFirstStep = () => { + currentStep.value = 'enterprise_info' +} +``` + +### 错误处理 +- 表单验证错误提示 +- API调用异常处理 +- 网络错误友好提示 +- 加载状态管理 + +### 性能优化 +- 组件懒加载 +- 表单数据缓存 +- 防抖处理 +- 内存清理 + +## 📱 移动端适配 + +### 布局调整 +- 垂直布局适配 +- 按钮全宽显示 +- 步骤图标缩小 +- 间距优化 + +### 交互优化 +- 触摸友好的按钮尺寸 +- 简化的表单布局 +- 优化的iframe高度 +- 响应式步骤条 + +## 🔌 第三方集成 + +### e签宝服务 +- **企业认证**: 统一身份认证 +- **合同签署**: 电子合同服务 +- **iframe集成**: 无缝嵌入体验 + +### 短信服务 +- **验证码发送**: 手机号验证 +- **倒计时功能**: 防止重复发送 +- **错误处理**: 发送失败重试 + +## 📊 状态流转 + +### 认证状态映射 +```javascript +const setCurrentStepByStatus = () => { + switch (certificationData.value.status) { + case 'not_started': + case 'pending': + currentStep.value = 'enterprise_info' + break + case 'info_submitted': + currentStep.value = 'enterprise_verify' + break + case 'enterprise_verified': + case 'contract_applied': + case 'contract_pending': + case 'contract_approved': + currentStep.value = 'contract_sign' + break + case 'contract_signed': + case 'completed': + currentStep.value = 'completed' + break + default: + currentStep.value = 'enterprise_info' + } +} +``` + +## 🧪 测试指南 + +### 开发模式测试 +1. 启用开发模式控制器 +2. 使用步骤选择器切换不同步骤 +3. 测试表单验证和提交 +4. 验证响应式布局 + +### 功能测试 +1. 表单验证测试 +2. 验证码发送测试 +3. iframe加载测试 +4. 状态切换测试 + +### 兼容性测试 +1. 不同浏览器测试 +2. 移动端适配测试 +3. 网络异常测试 +4. 性能压力测试 + +## 📝 使用说明 + +### 基本使用 +```vue + +``` + +### 配置步骤 +```javascript +const certificationSteps = [ + { + key: 'enterprise_info', + title: '填写企业信息', + description: '填写企业基本信息和法人信息', + icon: BuildingOfficeIcon + }, + // ... 其他步骤 +] +``` + +## 🔮 未来规划 + +### 功能增强 +- [ ] 支持多语言国际化 +- [ ] 增加进度保存功能 +- [ ] 支持离线填写 +- [ ] 增加数据导出功能 + +### 技术优化 +- [ ] 引入TypeScript支持 +- [ ] 增加单元测试覆盖 +- [ ] 优化包体积 +- [ ] 增加PWA支持 + +### 用户体验 +- [ ] 增加引导提示 +- [ ] 优化加载动画 +- [ ] 增加操作确认 +- [ ] 支持快捷键操作 + +## 📞 技术支持 + +如有问题或建议,请联系开发团队或提交Issue。 + +--- + +*最后更新: 2026年* \ No newline at end of file diff --git a/src/pages/certification/components/CertificationComplete.vue b/src/pages/certification/components/CertificationComplete.vue new file mode 100644 index 0000000..8d9ca65 --- /dev/null +++ b/src/pages/certification/components/CertificationComplete.vue @@ -0,0 +1,538 @@ + + + + + diff --git a/src/pages/certification/components/ContractExpired.vue b/src/pages/certification/components/ContractExpired.vue new file mode 100644 index 0000000..374b082 --- /dev/null +++ b/src/pages/certification/components/ContractExpired.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/src/pages/certification/components/ContractPreview.vue b/src/pages/certification/components/ContractPreview.vue new file mode 100644 index 0000000..ad6ec8b --- /dev/null +++ b/src/pages/certification/components/ContractPreview.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/src/pages/certification/components/ContractRejected.vue b/src/pages/certification/components/ContractRejected.vue new file mode 100644 index 0000000..785acdf --- /dev/null +++ b/src/pages/certification/components/ContractRejected.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/pages/certification/components/ContractSign.vue b/src/pages/certification/components/ContractSign.vue new file mode 100644 index 0000000..934b311 --- /dev/null +++ b/src/pages/certification/components/ContractSign.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/src/pages/certification/components/EnterpriseInfo.vue b/src/pages/certification/components/EnterpriseInfo.vue new file mode 100644 index 0000000..3d71ea3 --- /dev/null +++ b/src/pages/certification/components/EnterpriseInfo.vue @@ -0,0 +1,1287 @@ + + + + + + + diff --git a/src/pages/certification/components/EnterpriseVerify.vue b/src/pages/certification/components/EnterpriseVerify.vue new file mode 100644 index 0000000..4d706f3 --- /dev/null +++ b/src/pages/certification/components/EnterpriseVerify.vue @@ -0,0 +1,296 @@ + + + + + diff --git a/src/pages/certification/components/ManualReviewPending.vue b/src/pages/certification/components/ManualReviewPending.vue new file mode 100644 index 0000000..9ad4cd0 --- /dev/null +++ b/src/pages/certification/components/ManualReviewPending.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/pages/certification/index.vue b/src/pages/certification/index.vue new file mode 100644 index 0000000..935999d --- /dev/null +++ b/src/pages/certification/index.vue @@ -0,0 +1,791 @@ + + + + + diff --git a/src/pages/error/NotFound.vue b/src/pages/error/NotFound.vue new file mode 100644 index 0000000..51ffb69 --- /dev/null +++ b/src/pages/error/NotFound.vue @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/src/pages/finance/Finance.vue b/src/pages/finance/Finance.vue new file mode 100644 index 0000000..7ac5f92 --- /dev/null +++ b/src/pages/finance/Finance.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/pages/finance/Invoice.vue b/src/pages/finance/Invoice.vue new file mode 100644 index 0000000..21e95e9 --- /dev/null +++ b/src/pages/finance/Invoice.vue @@ -0,0 +1,1835 @@ + + + + + diff --git a/src/pages/finance/Transactions.vue b/src/pages/finance/Transactions.vue new file mode 100644 index 0000000..a433584 --- /dev/null +++ b/src/pages/finance/Transactions.vue @@ -0,0 +1,645 @@ + + + + + diff --git a/src/pages/finance/Wallet.vue b/src/pages/finance/Wallet.vue new file mode 100644 index 0000000..9aeb3f6 --- /dev/null +++ b/src/pages/finance/Wallet.vue @@ -0,0 +1,1695 @@ + + + + + diff --git a/src/pages/finance/WalletFail.vue b/src/pages/finance/WalletFail.vue new file mode 100644 index 0000000..94cbc1b --- /dev/null +++ b/src/pages/finance/WalletFail.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/src/pages/finance/WalletProcessing.vue b/src/pages/finance/WalletProcessing.vue new file mode 100644 index 0000000..1e255f2 --- /dev/null +++ b/src/pages/finance/WalletProcessing.vue @@ -0,0 +1,415 @@ + + + diff --git a/src/pages/finance/WalletSuccess.vue b/src/pages/finance/WalletSuccess.vue new file mode 100644 index 0000000..007a086 --- /dev/null +++ b/src/pages/finance/WalletSuccess.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/src/pages/finance/purchase-records/index.vue b/src/pages/finance/purchase-records/index.vue new file mode 100644 index 0000000..891e055 --- /dev/null +++ b/src/pages/finance/purchase-records/index.vue @@ -0,0 +1,555 @@ + + + + + diff --git a/src/pages/finance/recharge-records/index.vue b/src/pages/finance/recharge-records/index.vue new file mode 100644 index 0000000..99bb16c --- /dev/null +++ b/src/pages/finance/recharge-records/index.vue @@ -0,0 +1,503 @@ + + + + + diff --git a/src/pages/legal/LegalReader.vue b/src/pages/legal/LegalReader.vue new file mode 100644 index 0000000..0d76328 --- /dev/null +++ b/src/pages/legal/LegalReader.vue @@ -0,0 +1,656 @@ + + + + + diff --git a/src/pages/products/detail.vue b/src/pages/products/detail.vue new file mode 100644 index 0000000..9cf523b --- /dev/null +++ b/src/pages/products/detail.vue @@ -0,0 +1,2537 @@ + + + + + diff --git a/src/pages/products/index.vue b/src/pages/products/index.vue new file mode 100644 index 0000000..46c204d --- /dev/null +++ b/src/pages/products/index.vue @@ -0,0 +1,364 @@ + + + + + diff --git a/src/pages/profile/Profile.vue b/src/pages/profile/Profile.vue new file mode 100644 index 0000000..fc912d2 --- /dev/null +++ b/src/pages/profile/Profile.vue @@ -0,0 +1,1187 @@ + + + + + diff --git a/src/pages/profile/Settings.vue b/src/pages/profile/Settings.vue new file mode 100644 index 0000000..894eb2c --- /dev/null +++ b/src/pages/profile/Settings.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/pages/subscriptions/index.vue b/src/pages/subscriptions/index.vue new file mode 100644 index 0000000..697ce94 --- /dev/null +++ b/src/pages/subscriptions/index.vue @@ -0,0 +1,821 @@ + + + + + diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..b3d60f8 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,400 @@ +import { useUserStore } from '@/stores/user' +import { createRouter, createWebHistory } from 'vue-router' +import { statisticsRoutes } from './modules/statistics' + +// 路由配置 +const routes = [ + { + path: '/', + redirect: '/dashboard' + }, + { + path: '/auth', + component: () => import('@/layouts/AuthLayout.vue'), + meta: { requiresAuth: false }, + children: [ + { + path: 'login', + name: 'Login', + component: () => import('@/pages/auth/Login.vue'), + meta: { title: '登录' } + }, + { + path: 'register', + name: 'Register', + component: () => import('@/pages/auth/Register.vue'), + meta: { title: '注册' } + }, + { + path: 'reset', + name: 'ResetPassword', + component: () => import('@/pages/auth/ResetPassword.vue'), + meta: { title: '重置密码' } + }, + { + path: 'test', + name: 'TestStore', + component: () => import('@/pages/auth/TestStore.vue'), + meta: { title: 'Store测试' } + }, + { + path: 'response-test', + name: 'ResponseTest', + component: () => import('@/pages/auth/ResponseTest.vue'), + meta: { title: '响应格式测试' } + }, + { + path: 'integration-test', + name: 'AuthIntegrationTest', + component: () => import('@/pages/auth/AuthIntegrationTest.vue'), + meta: { title: '认证集成测试' } + } + ] + }, + { + path: '/products', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'Products', + component: () => import('@/pages/products/index.vue'), + meta: { title: '数据大厅' } + }, + { + path: ':id', + name: 'ProductDetail', + component: () => import('@/pages/products/detail.vue'), + meta: { title: '产品详情' } + } + ] + }, + { + path: '/subscriptions', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'Subscriptions', + component: () => import('@/pages/subscriptions/index.vue'), + meta: { title: '我的订阅' } + } + ] + }, + + { + path: '/finance', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'Finance', + component: () => import('@/pages/finance/Finance.vue'), + meta: { title: '财务管理' } + }, + { + path: 'invoice', + name: 'Invoice', + component: () => import('@/pages/finance/Invoice.vue'), + meta: { title: '发票申请' } + }, + { + path: 'wallet', + name: 'Wallet', + component: () => import('@/pages/finance/Wallet.vue'), + meta: { title: '余额充值' } + }, + { + path: 'wallet/success', + name: 'WalletSuccess', + component: () => import('@/pages/finance/WalletSuccess.vue'), + meta: { title: '充值成功' } + }, + { + path: 'wallet/fail', + name: 'WalletFail', + component: () => import('@/pages/finance/WalletFail.vue'), + meta: { title: '充值失败' } + }, + { + path: 'wallet/processing', + name: 'WalletProcessing', + component: () => import('@/pages/finance/WalletProcessing.vue'), + meta: { title: '支付处理中' } + }, + { + path: 'transactions', + name: 'Transactions', + component: () => import('@/pages/finance/Transactions.vue'), + meta: { title: '消费记录' } + }, + { + path: 'recharge-records', + name: 'FinanceRechargeRecords', + component: () => import('@/pages/finance/recharge-records/index.vue'), + meta: { title: '充值记录' } + }, + { + path: 'purchase-records', + name: 'FinancePurchaseRecords', + component: () => import('@/pages/finance/purchase-records/index.vue'), + meta: { title: '购买记录' } + } + ] + }, + { + path: '/apis', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + redirect: '/api/management' + }, + { + path: 'management', + name: 'ApiManagement', + component: () => import('@/pages/api/ApiManagement.vue'), + meta: { title: 'API管理', icon: 'api' } + }, + { + path: 'debugger', + name: 'ApiDebugger', + component: () => import('@/pages/api/ApiDebugger.vue'), + meta: { title: '在线调试', icon: 'bug' } + }, + { + path: 'usage', + name: 'ApiUsage', + component: () => import('@/pages/api/Usage.vue'), + meta: { title: '调用记录', icon: 'list' } + }, + { + path: 'whitelist', + name: 'ApiWhitelist', + component: () => import('@/pages/api/WhiteList.vue'), + meta: { title: '白名单管理', icon: 'shield' } + } + ] + }, + { + path: '/profile', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'Profile', + component: () => import('@/pages/profile/Profile.vue'), + meta: { title: '账户中心' } + }, + { + path: 'settings', + name: 'Settings', + component: () => import('@/pages/profile/Settings.vue'), + meta: { title: '我的订阅' } + }, + { + path: 'certification', + name: 'Certification', + component: () => import('@/pages/certification/index.vue'), + meta: { title: '企业入驻' }, + } + ] + }, + { + path: '/file-upload-test', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: false }, + children: [ + { + path: '', + name: 'FileUploadTest', + component: () => import('@/pages/FileUploadTest.vue'), + meta: { title: '文件上传组件测试' } + } + ] + }, + { + path: '/admin', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true, requiresAdmin: true }, + children: [ + { + path: '', + redirect: '/admin/products' + }, + { + path: 'products', + name: 'AdminProducts', + component: () => import('@/pages/admin/products/index.vue'), + meta: { title: '产品管理' } + }, + { + path: 'categories', + name: 'AdminCategories', + component: () => import('@/pages/admin/categories/index.vue'), + meta: { title: '分类管理' } + }, + { + path: 'subscriptions', + name: 'AdminSubscriptions', + component: () => import('@/pages/admin/subscriptions/index.vue'), + meta: { title: '订阅管理' } + }, + { + path: 'usage', + name: 'AdminUsage', + component: () => import('@/pages/admin/usage/index.vue'), + meta: { title: 'API调用记录管理' } + }, + { + path: 'transactions', + name: 'AdminTransactions', + component: () => import('@/pages/admin/transactions/index.vue'), + meta: { title: '消费记录管理' } + }, + { + path: 'recharge-records', + name: 'AdminRechargeRecords', + component: () => import('@/pages/admin/recharge-records/index.vue'), + meta: { title: '充值记录管理' } + }, + { + path: 'users', + name: 'AdminUsers', + component: () => import('@/pages/admin/users/index.vue'), + meta: { title: '用户管理' } + }, + { + path: 'invoices', + name: 'AdminInvoices', + component: () => import('@/pages/admin/invoices/index.vue'), + meta: { title: '发票管理' } + }, + { + path: 'articles', + name: 'AdminArticles', + component: () => import('@/pages/admin/articles/index.vue'), + meta: { title: '文章管理' } + }, + { + path: 'announcements', + name: 'AdminAnnouncements', + component: () => import('@/pages/admin/announcements/index.vue'), + meta: { title: '公告管理' } + }, + { + path: 'statistics', + name: 'AdminStatistics', + component: () => import('@/pages/admin/statistics/SystemStatisticsPage.vue'), + meta: { title: '系统统计' } + }, + { + path: 'ui-components', + name: 'AdminUIComponents', + component: () => import('@/pages/admin/ui-components/index.vue'), + meta: { title: '组件管理' } + }, + { + path: 'purchase-records', + name: 'AdminPurchaseRecords', + component: () => import('@/pages/admin/purchase-records/index.vue'), + meta: { title: '购买记录管理' } + }, + { + path: 'certification-reviews', + name: 'AdminCertificationReviews', + component: () => import('@/pages/admin/certification-reviews/index.vue'), + meta: { title: '企业审核' } + } + ] + }, + // 统计路由 + ...statisticsRoutes, + { + path: '/certification/callback/:scene', + component: () => import('@/pages/certification/IframeCallback.vue'), + meta: { requiresAuth: false }, + }, + { + path: '/legal/:doc', + name: 'LegalReader', + component: () => import('@/pages/legal/LegalReader.vue'), + meta: { requiresAuth: false, title: '法律与合规' } + }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: () => import('@/pages/error/NotFound.vue'), + meta: { title: '页面不存在' } + } +] + +// 创建路由实例 +const router = createRouter({ + history: createWebHistory(), + routes, + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition + } else { + return { top: 0 } + } + } +}) + +// 路由守卫 +router.beforeEach(async (to, from, next) => { + const userStore = useUserStore() + + // 对于不需要认证的路由(如登录页),不等待初始化,直接放行 + const isAuthRoute = to.path.startsWith('/auth') + const requiresAuth = to.meta.requiresAuth + + // 只有在需要认证的路由上才等待初始化 + if (requiresAuth && !userStore.initialized) { + await userStore.init() + } else if (!userStore.initialized) { + // 对于不需要认证的路由,异步初始化但不阻塞 + userStore.init().catch(err => { + console.warn('UserStore初始化失败:', err) + }) + } + + // 设置页面标题 + if (to.meta.title) { + document.title = `${to.meta.title} - 海宇数据控制台` + } + + // 检查是否需要认证 + if (requiresAuth && !userStore.isLoggedIn) { + next('/auth/login') + return + } + + // 检查管理员权限 + if (to.meta.requiresAdmin && !userStore.isAdmin) { + next('/products') + return + } + + // 已登录用户访问认证页面,重定向到数据大厅 + if (isAuthRoute && userStore.isLoggedIn) { + next('/products') + return + } + + next() +}) + +// 路由后置守卫 +router.afterEach(() => { + // 可以在这里添加路由切换后的逻辑 + // 比如埋点统计、页面访问记录等 +}) + +export default router diff --git a/src/router/modules/statistics.js b/src/router/modules/statistics.js new file mode 100644 index 0000000..62d4f04 --- /dev/null +++ b/src/router/modules/statistics.js @@ -0,0 +1,39 @@ +// 统计模块路由配置 - 简化版 +export const statisticsRoutes = [ + { + path: '/dashboard', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'Dashboard', + component: () => import('@/views/statistics/UserStatisticsPage.vue'), + meta: { + title: '仪表盘', + icon: 'el-icon-data-board', + requiresAuth: true, + roles: ['user', 'admin', 'manager', 'analyst'] + } + } + ] + }, + { + path: '/statistics', + component: () => import('@/layouts/MainLayout.vue'), + meta: { requiresAuth: true }, + children: [ + { + path: '', + name: 'UserStatistics', + component: () => import('@/views/statistics/UserStatisticsPage.vue'), + meta: { + title: '我的统计', + icon: 'el-icon-data-board', + requiresAuth: true, + roles: ['user', 'admin', 'manager', 'analyst'] + } + } + ] + } +] diff --git a/src/stores/app.js b/src/stores/app.js new file mode 100644 index 0000000..7f4a068 --- /dev/null +++ b/src/stores/app.js @@ -0,0 +1,163 @@ + + +export const useAppStore = defineStore('app', () => { + // 应用状态 + const sidebarCollapsed = ref(false) + const mobileSidebarOpen = ref(false) + const theme = ref('light') + const language = ref('zh-CN') + const loading = ref(false) + const notifications = ref([]) + const isMobile = ref(false) + + // 计算属性:根据设备类型判断侧边栏状态 + const sidebarVisible = computed(() => { + if (isMobile.value) { + return mobileSidebarOpen.value + } + return !sidebarCollapsed.value + }) + + // 切换侧边栏 + const toggleSidebar = () => { + if (isMobile.value) { + mobileSidebarOpen.value = !mobileSidebarOpen.value + } else { + sidebarCollapsed.value = !sidebarCollapsed.value + localStorage.setItem('sidebarCollapsed', JSON.stringify(sidebarCollapsed.value)) + } + } + + // 设置侧边栏状态 + const setSidebarCollapsed = (collapsed) => { + if (isMobile.value) { + mobileSidebarOpen.value = !collapsed + } else { + sidebarCollapsed.value = collapsed + localStorage.setItem('sidebarCollapsed', JSON.stringify(collapsed)) + } + } + + // 关闭移动端侧边栏 + const closeMobileSidebar = () => { + if (isMobile.value) { + mobileSidebarOpen.value = false + } + } + + // 设置移动端状态 + const setMobileState = (mobile) => { + isMobile.value = mobile + if (mobile) { + mobileSidebarOpen.value = false + } + } + + // 切换主题 + const toggleTheme = () => { + theme.value = theme.value === 'light' ? 'dark' : 'light' + localStorage.setItem('theme', theme.value) + } + + // 设置主题 + const setTheme = (newTheme) => { + theme.value = newTheme + localStorage.setItem('theme', newTheme) + } + + // 设置语言 + const setLanguage = (lang) => { + language.value = lang + localStorage.setItem('language', lang) + } + + // 设置加载状态 + const setLoading = (status) => { + loading.value = status + } + + // 添加通知 + const addNotification = (notification) => { + const id = Date.now().toString() + notifications.value.push({ + id, + ...notification, + timestamp: new Date() + }) + + // 自动移除通知(5秒后) + setTimeout(() => { + removeNotification(id) + }, 5000) + } + + // 移除通知 + const removeNotification = (id) => { + const index = notifications.value.findIndex(n => n.id === id) + if (index > -1) { + notifications.value.splice(index, 1) + } + } + + // 清空所有通知 + const clearNotifications = () => { + notifications.value = [] + } + + // 初始化应用配置 + const initAppConfig = () => { + // 从localStorage恢复配置 + const savedTheme = localStorage.getItem('theme') + const savedLanguage = localStorage.getItem('language') + const savedSidebarCollapsed = localStorage.getItem('sidebarCollapsed') + + if (savedTheme) theme.value = savedTheme + if (savedLanguage) language.value = savedLanguage + if (savedSidebarCollapsed) sidebarCollapsed.value = JSON.parse(savedSidebarCollapsed) + + // 检测移动端 + const checkMobile = () => { + const mobile = window.innerWidth <= 768 + setMobileState(mobile) + } + + // 初始检测 + checkMobile() + + // 监听窗口大小变化 + window.addEventListener('resize', checkMobile) + + // 返回清理函数 + return () => { + window.removeEventListener('resize', checkMobile) + } + } + + return { + // 状态 + sidebarCollapsed, + mobileSidebarOpen, + theme, + language, + loading, + notifications, + isMobile, + + // 计算属性 + sidebarVisible, + + // 方法 + toggleSidebar, + setSidebarCollapsed, + closeMobileSidebar, + setMobileState, + toggleTheme, + setTheme, + setLanguage, + setLoading, + addNotification, + removeNotification, + clearNotifications, + initAppConfig + } +}) diff --git a/src/stores/counter.js b/src/stores/counter.js new file mode 100644 index 0000000..2b35bec --- /dev/null +++ b/src/stores/counter.js @@ -0,0 +1,11 @@ + + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/src/stores/user.js b/src/stores/user.js new file mode 100644 index 0000000..5ffee25 --- /dev/null +++ b/src/stores/user.js @@ -0,0 +1,538 @@ +/** + * 用户状态管理 Store + * + * 后端响应格式说明: + * 所有API响应都被包装在标准格式中: + * { + * "success": true, + * "data": { ... }, // 真正的数据在这里 + * "message": "操作成功", + * "requestId": "", + * "timestamp": 1752469413 + * } + * + * 使用示例: + * + * // 在组件中使用 + * import { useUserStore } from '@/stores/user' + * + * const userStore = useUserStore() + * + * // 登录 + * const loginData = { + * method: 'sms', // 或 'password' + * phone: '13800138000', + * code: '123456', // 短信登录时需要 + * password: 'password123' // 密码登录时需要 + * } + * const result = await userStore.login(loginData) + * if (result.success) { + * console.log('登录成功') + * // result.data 包含: { user, access_token, token_type, expires_in, login_method } + * } + * + * // 注册 + * const registerData = { + * phone: '13800138000', + * password: 'password123', + * confirmPassword: 'password123', + * code: '123456' + * } + * const result = await userStore.register(registerData) + * if (result.success) { + * // result.data 包含注册成功的数据 + * } + * + * // 发送验证码 + * const result = await userStore.sendCode('13800138000', 'register') + * if (result.success) { + * // result.data 包含验证码发送结果 + * } + * + * // 重置密码 + * const resetData = { + * phone: '13800138000', + * newPassword: 'newpassword123', + * confirmNewPassword: 'newpassword123', + * code: '123456' + * } + * const result = await userStore.resetPassword(resetData) + * if (result.success) { + * // result.data 包含重置密码结果 + * } + * + * // 获取用户信息 + * const result = await userStore.fetchUserProfile() + * if (result.success) { + * // result.data 包含用户信息: { id, phone, created_at, updated_at } + * } + * + * // 修改密码 + * const passwordData = { + * oldPassword: 'oldpassword123', + * newPassword: 'newpassword123', + * confirmNewPassword: 'newpassword123', + * code: '123456' + * } + * const result = await userStore.changePassword(passwordData) + * if (result.success) { + * // result.data 包含修改密码结果 + * } + * + * // 登出 + * userStore.logout() + * + * // 检查登录状态 + * const isAuth = await userStore.checkAuth() + * + * // 响应式状态 + * console.log(userStore.isLoggedIn) // 是否已登录 + * console.log(userStore.userInfo) // 用户信息: { id, phone, created_at, updated_at } + * console.log(userStore.loading) // 加载状态 + */ + +import { userApi } from '@/api' +import router from '@/router' +import { authEventBus } from '@/utils/request' +import { generateSMSRequest } from '@/utils/smsSignature' +import { clearLocalVersions, saveLocalVersions, VERSION_CONFIG, versionChecker } from '@/utils/version' +import { ElMessage } from 'element-plus' + +export const useUserStore = defineStore('user', () => { + // 状态 + const user = ref(null) + const accessToken = ref(localStorage.getItem('access_token') || '') + const tokenType = ref(localStorage.getItem('token_type') || 'Bearer') + const isAuthenticated = ref(false) + const loading = ref(false) + // 新增:初始化标志 + const initialized = ref(false) + + // 版本管理 + const tokenVersion = ref(localStorage.getItem('token_version') || VERSION_CONFIG.TOKEN_VERSION) + const appVersion = ref(localStorage.getItem('app_version') || VERSION_CONFIG.APP_VERSION) + + // 计算属性 + const isLoggedIn = computed(() => { + return isAuthenticated.value && !!accessToken.value + }) + + const userInfo = computed(() => user.value) + + // 新增:用户类型相关计算属性 + const isAdmin = computed(() => { + return user.value?.user_type === 'admin' + }) + + const userType = computed(() => { + return user.value?.user_type || 'user' + }) + + // 新增:用户认证状态计算属性 + const isCertified = computed(() => { + return user.value?.is_certified || false + }) + + // 检查用户信息是否完整 + const isUserInfoComplete = computed(() => { + return user.value && + user.value.id && + user.value.phone && + user.value.user_type !== undefined + }) + + // 强制刷新用户信息 + const refreshUserProfile = async () => { + if (!accessToken.value) { + return { success: false, error: '未登录' } + } + + loading.value = true + try { + const result = await fetchUserProfile() + return result + } catch (error) { + return { success: false, error } + } finally { + loading.value = false + } + } + + const hasRole = (role) => { + return userType.value === role + } + + // 监听认证错误事件 + const handleAuthError = (message) => { + console.log('用户store收到认证错误事件:', message) + logout() + } + + // 处理版本退出登录事件 + const handleVersionLogout = (event) => { + console.log('收到版本退出登录事件:', event.detail) + logout() + ElMessage.error('系统已更新,请重新登录') + router.push('/auth/login') + } + + // 处理版本刷新事件 + const handleVersionRefresh = (event) => { + console.log('收到版本刷新事件:', event.detail) + saveLocalVersions({ appVersion: VERSION_CONFIG.APP_VERSION }) + ElMessage.success('应用已更新,正在刷新页面...') + setTimeout(() => { + window.location.reload() + }, 1500) + } + + // 版本检查 + const checkVersions = () => { + const currentTokenVersion = localStorage.getItem('token_version') || VERSION_CONFIG.TOKEN_VERSION + const currentAppVersion = localStorage.getItem('app_version') || VERSION_CONFIG.APP_VERSION + + // 检查token版本 + if (currentTokenVersion !== VERSION_CONFIG.TOKEN_VERSION) { + console.warn('Token版本不匹配,需要重新登录', { + current: currentTokenVersion, + expected: VERSION_CONFIG.TOKEN_VERSION + }) + + // 清除所有认证相关数据 + logout() + + // 显示提示信息 + ElMessage.error('系统已更新,请重新登录') + + // 跳转到登录页面 + router.push('/auth/login') + return false + } + + // 检查应用版本 + if (currentAppVersion !== VERSION_CONFIG.APP_VERSION) { + console.log('应用版本已更新,刷新页面', { + current: currentAppVersion, + expected: VERSION_CONFIG.APP_VERSION + }) + + // 更新本地存储的应用版本 + saveLocalVersions({ appVersion: VERSION_CONFIG.APP_VERSION }) + + // 显示提示信息 + ElMessage.success('应用已更新,正在刷新页面...') + + // 延迟刷新页面 + setTimeout(() => { + window.location.reload() + }, 1500) + + return false + } + + return true + } + + // 登录 + const login = async (loginData) => { + loading.value = true + try { + let response + if (loginData.method === 'sms') { + response = await userApi.loginWithSMS({ + phone: loginData.phone, + code: loginData.code + }) + } else { + response = await userApi.loginWithPassword({ + phone: loginData.phone, + password: loginData.password + }) + } + + // 后端返回格式: { success: true, data: { user, access_token, token_type, ... }, message, ... } + // 真正的数据在 response.data 中 + const responseData = response.data + + // 保存token和用户信息 + accessToken.value = responseData.access_token + tokenType.value = responseData.token_type + user.value = responseData.user + isAuthenticated.value = true + + // 保存到localStorage + localStorage.setItem('access_token', responseData.access_token) + localStorage.setItem('token_type', responseData.token_type) + saveLocalVersions({ tokenVersion: VERSION_CONFIG.TOKEN_VERSION }) + + // 登录成功后,主动获取最新的用户信息以确保数据完整 + try { + const profileResult = await fetchUserProfile() + if (profileResult.success) { + console.log('登录后用户信息更新成功') + // 如果获取到更完整的用户信息,更新用户数据 + if (profileResult.data) { + user.value = { ...user.value, ...profileResult.data } + } + } + } catch (profileError) { + console.warn('登录后获取用户信息失败:', profileError) + // 即使获取用户信息失败,也不影响登录流程 + } + + return { success: true, data: responseData } + } catch (error) { + console.error('登录失败:', error) + return { success: false, error } + } finally { + loading.value = false + } + } + + // 注册 + const register = async (registerData) => { + loading.value = true + try { + const response = await userApi.register({ + phone: registerData.phone, + password: registerData.password, + confirm_password: registerData.confirmPassword, + code: registerData.code + }) + + // 后端返回格式: { success: true, data: {...}, message, ... } + return { success: true, data: response.data } + } catch (error) { + return { success: false, error } + } finally { + loading.value = false + } + } + + // 重置密码 + const resetPassword = async (resetData) => { + loading.value = true + try { + const response = await userApi.resetPassword({ + phone: resetData.phone, + new_password: resetData.newPassword, + confirm_new_password: resetData.confirmNewPassword, + code: resetData.code + }) + + // 后端返回格式: { success: true, data: {...}, message, ... } + return { success: true, data: response.data } + } catch (error) { + return { success: false, error } + } finally { + loading.value = false + } + } + + // 发送验证码(使用自定义编码和签名) + const sendCode = async (phone, scene, captchaVerifyParam = null) => { + try { + // 1. 生成签名并编码请求数据 + const encodedRequest = await generateSMSRequest(phone, scene) + + // 2. 如果有滑块验证码参数,添加到请求数据中 + if (captchaVerifyParam) { + encodedRequest.captchaVerifyParam = captchaVerifyParam + } + + // 3. 发送编码后的请求 + const response = await userApi.sendCode(encodedRequest) + + // 后端返回格式: { success: true, data: {...}, message, ... } + return { success: true, data: response.data } + } catch (error) { + console.error('发送验证码失败:', error) + return { success: false, error } + } + } + + // 获取用户信息 + const fetchUserProfile = async () => { + loading.value = true + try { + const response = await userApi.getProfile() + // 后端返回格式: { success: true, data: {...}, message, ... } + const userData = response.data + user.value = userData + isAuthenticated.value = true + + // 记录认证状态变化 + if (userData.is_certified !== undefined) { + console.log('用户认证状态:', userData.is_certified ? '已认证' : '未认证') + } + + return { success: true, data: userData } + } catch (error) { + return { success: false, error } + } finally { + loading.value = false + } + } + + // 修改密码 + const changePassword = async (passwordData) => { + loading.value = true + try { + const response = await userApi.changePassword({ + old_password: passwordData.oldPassword, + new_password: passwordData.newPassword, + confirm_new_password: passwordData.confirmNewPassword, + code: passwordData.code + }) + + // 后端返回格式: { success: true, data: {...}, message, ... } + return { success: true, data: response.data } + } catch (error) { + return { success: false, error } + } finally { + loading.value = false + } + } + + // 登出 + const logout = () => { + // 清除状态 + user.value = null + accessToken.value = '' + tokenType.value = 'Bearer' + isAuthenticated.value = false + loading.value = false + + // 清除localStorage + localStorage.removeItem('access_token') + localStorage.removeItem('token_type') + clearLocalVersions() + } + + // 检查登录状态 + const checkAuth = async () => { + if (!accessToken.value) { + return false + } + + try { + const result = await fetchUserProfile() + return result.success + } catch (error) { + logout() + return false + } + } + + // 初始化标志,防止重复初始化 + let isInitializing = false + + // 初始化 + const init = async () => { + // 如果已经初始化完成,直接返回 + if (initialized.value) { + return + } + + // 如果正在初始化,等待完成 + if (isInitializing) { + // 等待初始化完成 + while (isInitializing) { + await new Promise(resolve => setTimeout(resolve, 50)) + } + return + } + + isInitializing = true + + try { + // 监听认证错误事件(只注册一次) + if (!authEventBus.listeners.includes(handleAuthError)) { + authEventBus.onAuthError(handleAuthError) + } + + // 监听版本更新事件(只注册一次) + if (!window.hasVersionListeners) { + window.addEventListener('version:logout', handleVersionLogout) + window.addEventListener('version:refresh', handleVersionRefresh) + window.hasVersionListeners = true + } + + // 进行版本检查 + if (!checkVersions()) { + initialized.value = true + return + } + + if (accessToken.value && !user.value) { + // 有token但无用户信息,自动拉取 + loading.value = true + try { + const result = await fetchUserProfile() + isAuthenticated.value = result.success + + // 如果认证成功,启动版本检查器 + if (result.success) { + versionChecker.startAutoCheck() + } + } catch { + isAuthenticated.value = false + logout() + } finally { + loading.value = false + initialized.value = true + } + } else { + // 如果已经认证,启动版本检查器 + if (isAuthenticated.value) { + versionChecker.startAutoCheck() + } + initialized.value = true + } + } finally { + isInitializing = false + } + } + + // 清理 + const cleanup = () => { + // 停止版本检查器 + versionChecker.stopAutoCheck() + + // 移除事件监听器 + window.removeEventListener('version:logout', handleVersionLogout) + window.removeEventListener('version:refresh', handleVersionRefresh) + } + + return { + // 状态 + user, + accessToken, + tokenType, + isAuthenticated, + loading, + initialized, // 新增 + + // 计算属性 + isLoggedIn, + userInfo, + isAdmin, + userType, + isCertified, // 新增 + isUserInfoComplete, // 新增 + hasRole, + + // 方法 + login, + register, + resetPassword, + sendCode, + fetchUserProfile, + changePassword, + logout, + checkAuth, + init, + cleanup, + refreshUserProfile // 新增 + } +}) diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..5581b34 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,295 @@ +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' + +// 设置dayjs语言 +dayjs.locale('zh-cn') + +/** + * 日期格式化 + * @param {Date|string} date 日期 + * @param {string} format 格式 + * @returns {string} 格式化后的日期字符串 + */ +export const formatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => { + if (!date) return '' + return dayjs(date).format(format) +} + +/** + * 相对时间 + * @param {Date|string} date 日期 + * @returns {string} 相对时间字符串 + */ +export const fromNow = (date) => { + if (!date) return '' + return dayjs(date).fromNow() +} + +/** + * 手机号格式化 + * @param {string} phone 手机号 + * @returns {string} 格式化后的手机号 + */ +export const formatPhone = (phone) => { + if (!phone) return '' + return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3') +} + +/** + * 金额格式化 + * @param {number} amount 金额 + * @param {number} decimals 小数位数 + * @returns {string} 格式化后的金额 + */ +export const formatMoney = (amount, decimals = 2) => { + if (amount === null || amount === undefined) return '0.00' + return Number(amount).toFixed(decimals) +} + +/** + * 文件大小格式化 + * @param {number} bytes 字节数 + * @returns {string} 格式化后的文件大小 + */ +export const formatFileSize = (bytes) => { + 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 parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] +} + +/** + * 防抖函数 + * @param {Function} func 要防抖的函数 + * @param {number} wait 等待时间 + * @returns {Function} 防抖后的函数 + */ +export const debounce = (func, wait) => { + let timeout + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout) + func(...args) + } + clearTimeout(timeout) + timeout = setTimeout(later, wait) + } +} + +/** + * 节流函数 + * @param {Function} func 要节流的函数 + * @param {number} limit 限制时间 + * @returns {Function} 节流后的函数 + */ +export const throttle = (func, limit) => { + let inThrottle + return function() { + const args = arguments + const context = this + if (!inThrottle) { + func.apply(context, args) + inThrottle = true + setTimeout(() => inThrottle = false, limit) + } + } +} + +/** + * 深拷贝 + * @param {any} obj 要拷贝的对象 + * @returns {any} 拷贝后的对象 + */ +export const deepClone = (obj) => { + if (obj === null || typeof obj !== 'object') return obj + if (obj instanceof Date) return new Date(obj.getTime()) + if (obj instanceof Array) return obj.map(item => deepClone(item)) + if (typeof obj === 'object') { + const clonedObj = {} + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + clonedObj[key] = deepClone(obj[key]) + } + } + return clonedObj + } +} + +/** + * 生成UUID + * @returns {string} UUID字符串 + */ +export const generateUUID = () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0 + const v = c == 'x' ? r : (r & 0x3 | 0x8) + return v.toString(16) + }) +} + +/** + * 验证手机号 + * @param {string} phone 手机号 + * @returns {boolean} 是否有效 + */ +export const validatePhone = (phone) => { + const phoneRegex = /^1[3-9]\d{9}$/ + return phoneRegex.test(phone) +} + +/** + * 验证邮箱 + * @param {string} email 邮箱 + * @returns {boolean} 是否有效 + */ +export const validateEmail = (email) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return emailRegex.test(email) +} + +/** + * 验证身份证号 + * @param {string} idCard 身份证号 + * @returns {boolean} 是否有效 + */ +export const validateIdCard = (idCard) => { + const idCardRegex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ + return idCardRegex.test(idCard) +} + +/** + * 获取URL参数 + * @param {string} name 参数名 + * @returns {string|null} 参数值 + */ +export const getUrlParam = (name) => { + const urlParams = new URLSearchParams(window.location.search) + return urlParams.get(name) +} + +/** + * 设置URL参数 + * @param {string} name 参数名 + * @param {string} value 参数值 + */ +export const setUrlParam = (name, value) => { + const url = new URL(window.location) + url.searchParams.set(name, value) + window.history.replaceState({}, '', url) +} + +/** + * 移除URL参数 + * @param {string} name 参数名 + */ +export const removeUrlParam = (name) => { + const url = new URL(window.location) + url.searchParams.delete(name) + window.history.replaceState({}, '', url) +} + +/** + * 复制文本到剪贴板 + * @param {string} text 要复制的文本 + * @returns {Promise} 是否复制成功 + */ +export const copyToClipboard = async (text) => { + try { + await navigator.clipboard.writeText(text) + return true + } catch (err) { + // 降级方案 + const textArea = document.createElement('textarea') + textArea.value = text + document.body.appendChild(textArea) + textArea.select() + try { + document.execCommand('copy') + document.body.removeChild(textArea) + return true + } catch (err) { + document.body.removeChild(textArea) + return false + } + } +} + +/** + * 下载文件 + * @param {string} url 文件URL + * @param {string} filename 文件名 + */ +export const downloadFile = (url, filename) => { + const link = document.createElement('a') + link.href = url + link.download = filename + document.body.appendChild(link) + link.click() + document.body.removeChild(link) +} + +/** + * 获取浏览器信息 + * @returns {object} 浏览器信息 + */ +export const getBrowserInfo = () => { + const ua = navigator.userAgent + const browser = { + name: '', + version: '', + os: '' + } + + // 检测浏览器 + if (ua.includes('Chrome')) { + browser.name = 'Chrome' + } else if (ua.includes('Firefox')) { + browser.name = 'Firefox' + } else if (ua.includes('Safari')) { + browser.name = 'Safari' + } else if (ua.includes('Edge')) { + browser.name = 'Edge' + } else if (ua.includes('MSIE') || ua.includes('Trident')) { + browser.name = 'IE' + } + + // 检测操作系统 + if (ua.includes('Windows')) { + browser.os = 'Windows' + } else if (ua.includes('Mac')) { + browser.os = 'Mac' + } else if (ua.includes('Linux')) { + browser.os = 'Linux' + } else if (ua.includes('Android')) { + browser.os = 'Android' + } else if (ua.includes('iOS')) { + browser.os = 'iOS' + } + + return browser +} + +/** + * 检查是否为移动设备 + * @returns {boolean} 是否为移动设备 + */ +export const isMobile = () => { + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) +} + +/** + * 检查是否为微信浏览器 + * @returns {boolean} 是否为微信浏览器 + */ +export const isWeChat = () => { + return /MicroMessenger/i.test(navigator.userAgent) +} + +/** + * 检查是否为支付宝浏览器 + * @returns {boolean} 是否为支付宝浏览器 + */ +export const isAlipay = () => { + return /AlipayClient/i.test(navigator.userAgent) +} diff --git a/src/utils/legalMarkdown.js b/src/utils/legalMarkdown.js new file mode 100644 index 0000000..7130fb4 --- /dev/null +++ b/src/utils/legalMarkdown.js @@ -0,0 +1,72 @@ +import { marked } from 'marked' + +/** + * 从行内/标题中去掉常见 Markdown 标记,供目录纯文本显示 + * @param {string} s + * @returns {string} + */ +function stripHeadingMarks(s) { + return s + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\*(.+?)\*/g, '$1') + .replace(/`(.+?)`/g, '$1') + .replace(/\[([^\]]+)]\([^)]+\)/g, '$1') + .trim() +} + +let sectionCounter = 0 + +marked.use({ + renderer: { + heading({ tokens, depth }) { + const inner = this.parser.parseInline(tokens) + if (depth === 1) { + return `

${inner}

\n` + } + if (depth === 2 || depth === 3) { + sectionCounter += 1 + const id = `sec-${sectionCounter}` + return `${inner}\n` + } + return `${inner}\n` + } + } +}) + +/** + * 解析正文中 ## / ### 标题,生成与渲染器一致的锚点 id(仅 h2/h3 计入 sec-*) + * @param {string} md + * @returns {{ id: string, level: number, text: string }[]} + */ +export function buildTocFromMarkdown(md) { + if (!md) { + return [] + } + const items = [] + let n = 0 + for (const line of md.split('\n')) { + const t = line.trim() + const m = /^(##|###)\s+(.+)$/.exec(t) + if (m) { + n += 1 + items.push({ + id: `sec-${n}`, + level: m[1] === '##' ? 2 : 3, + text: stripHeadingMarks(m[2]) + }) + } + } + return items +} + +/** + * @param {string} md + * @returns {string} + */ +export function renderLegalMarkdown(md) { + sectionCounter = 0 + if (!md) { + return '' + } + return marked.parse(md, { async: false }) +} diff --git a/src/utils/performance.js b/src/utils/performance.js new file mode 100644 index 0000000..2818d3c --- /dev/null +++ b/src/utils/performance.js @@ -0,0 +1,193 @@ +/** + * 渲染性能优化工具函数 + */ + +// 检测设备性能等级 +export const getDevicePerformanceLevel = () => { + const hardwareConcurrency = navigator.hardwareConcurrency || 4 + const memory = navigator.deviceMemory || 4 + const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) + const isLowEnd = hardwareConcurrency <= 4 || memory <= 4 || isMobile + + if (isLowEnd) { + return 'low' + } else if (hardwareConcurrency <= 8 || memory <= 8) { + return 'medium' + } else { + return 'high' + } +} + +// 检测是否支持硬件加速 +export const supportsHardwareAcceleration = () => { + const canvas = document.createElement('canvas') + const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') + return !!gl +} + +// 检测是否支持backdrop-filter +export const supportsBackdropFilter = () => { + return CSS.supports('backdrop-filter', 'blur(1px)') +} + +// 检测用户是否偏好减少动画 +export const prefersReducedMotion = () => { + return window.matchMedia('(prefers-reduced-motion: reduce)').matches +} + +// 应用渲染性能优化策略 +export const applyRenderOptimizations = () => { + const performanceLevel = getDevicePerformanceLevel() + const supportsHardware = supportsHardwareAcceleration() + const supportsBackdrop = supportsBackdropFilter() + const reducedMotion = prefersReducedMotion() + + // 添加性能等级类到body + document.body.classList.add(`performance-${performanceLevel}`) + + // 如果不支持硬件加速,添加降级类 + if (!supportsHardware) { + document.body.classList.add('no-hardware-acceleration') + } + + // 如果不支持backdrop-filter,添加降级类 + if (!supportsBackdrop) { + document.body.classList.add('no-backdrop-filter') + } + + // 如果用户偏好减少动画,添加相应类 + if (reducedMotion) { + document.body.classList.add('reduced-motion') + } + + return { + performanceLevel, + supportsHardware, + supportsBackdrop, + reducedMotion + } +} + +// 懒加载优化 +export const createIntersectionObserver = (callback, options = {}) => { + const defaultOptions = { + root: null, + rootMargin: '50px', + threshold: 0.1, + ...options + } + + return new IntersectionObserver(callback, defaultOptions) +} + +// 防抖函数 +export const debounce = (func, wait) => { + let timeout + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout) + func(...args) + } + clearTimeout(timeout) + timeout = setTimeout(later, wait) + } +} + +// 节流函数 +export const throttle = (func, limit) => { + let inThrottle + return function() { + const args = arguments + const context = this + if (!inThrottle) { + func.apply(context, args) + inThrottle = true + setTimeout(() => inThrottle = false, limit) + } + } +} + +// 优化图片加载 +export const optimizeImageLoading = () => { + const images = document.querySelectorAll('img[data-src]') + const imageObserver = createIntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target + img.src = img.dataset.src + img.classList.remove('lazy') + imageObserver.unobserve(img) + } + }) + }) + + images.forEach(img => imageObserver.observe(img)) +} + +// 优化动画性能 +export const optimizeAnimations = () => { + const animatedElements = document.querySelectorAll('[class*="animate-"]') + + animatedElements.forEach(element => { + // 添加硬件加速 + element.style.transform = 'translateZ(0)' + element.style.willChange = 'transform' + }) +} + +// 减少重绘和重排 +export const optimizeLayout = () => { + // 批量DOM操作 + const batchDOMUpdates = (updates) => { + requestAnimationFrame(() => { + updates.forEach(update => update()) + }) + } + + return { batchDOMUpdates } +} + +// 内存优化 +export const optimizeMemory = () => { + // 清理事件监听器 + const cleanupEventListeners = () => { + // 在组件卸载时调用 + } + + // 清理定时器 + const cleanupTimers = () => { + // 清理所有定时器 + } + + return { cleanupEventListeners, cleanupTimers } +} + +// 初始化渲染性能优化 +export const initRenderOptimizations = () => { + // 应用基础优化 + const config = applyRenderOptimizations() + + // 根据性能等级应用不同策略 + switch (config.performanceLevel) { + case 'low': + // 低端设备:禁用复杂动画,简化UI + document.body.classList.add('low-performance-mode') + break + case 'medium': + // 中端设备:适度优化 + document.body.classList.add('medium-performance-mode') + break + case 'high': + // 高端设备:启用所有功能 + document.body.classList.add('high-performance-mode') + break + } + + // 优化图片加载 + optimizeImageLoading() + + // 优化动画 + optimizeAnimations() + + return config +} diff --git a/src/utils/permission.js b/src/utils/permission.js new file mode 100644 index 0000000..c5f32c7 --- /dev/null +++ b/src/utils/permission.js @@ -0,0 +1,242 @@ +// 权限指令 +export const permission = { + inserted(el, binding) { + const { value } = binding + const roles = value.roles || [] + const permissions = value.permissions || [] + const userRole = el._vue.$store.getters.userRole || 'user' + const userPermissions = el._vue.$store.getters.userPermissions || [] + + let hasPermission = true + + // 检查角色权限 + if (roles.length > 0) { + if (!roles.includes(userRole)) { + hasPermission = false + } + } + + // 检查功能权限 + if (permissions.length > 0) { + const hasAllPermissions = permissions.every(permission => + userPermissions.includes(permission) + ) + if (!hasAllPermissions) { + hasPermission = false + } + } + + if (!hasPermission) { + el.style.display = 'none' + } + }, + + update(el, binding) { + const { value } = binding + const roles = value.roles || [] + const permissions = value.permissions || [] + const userRole = el._vue.$store.getters.userRole || 'user' + const userPermissions = el._vue.$store.getters.userPermissions || [] + + let hasPermission = true + + // 检查角色权限 + if (roles.length > 0) { + if (!roles.includes(userRole)) { + hasPermission = false + } + } + + // 检查功能权限 + if (permissions.length > 0) { + const hasAllPermissions = permissions.every(permission => + userPermissions.includes(permission) + ) + if (!hasAllPermissions) { + hasPermission = false + } + } + + el.style.display = hasPermission ? '' : 'none' + } +} + +// 角色指令 +export const role = { + inserted(el, binding) { + const { value } = binding + const userRole = el._vue.$store.getters.userRole || 'user' + + if (!value.includes(userRole)) { + el.style.display = 'none' + } + }, + + update(el, binding) { + const { value } = binding + const userRole = el._vue.$store.getters.userRole || 'user' + + el.style.display = value.includes(userRole) ? '' : 'none' + } +} + +// 权限检查工具函数 +export const checkPermission = { + // 检查角色权限 + hasRole(userRole, requiredRoles) { + if (!requiredRoles || requiredRoles.length === 0) { + return true + } + return requiredRoles.includes(userRole) + }, + + // 检查功能权限 + hasPermission(userPermissions, requiredPermissions) { + if (!requiredPermissions || requiredPermissions.length === 0) { + return true + } + return requiredPermissions.every(permission => + userPermissions.includes(permission) + ) + }, + + // 检查综合权限 + hasAccess(userRole, userPermissions, requiredRoles, requiredPermissions) { + const hasRoleAccess = this.hasRole(userRole, requiredRoles) + const hasPermissionAccess = this.hasPermission(userPermissions, requiredPermissions) + return hasRoleAccess && hasPermissionAccess + } +} + +// 权限常量 +export const PERMISSIONS = { + // 统计相关权限 + STATISTICS_VIEW: 'statistics:view', + STATISTICS_CREATE: 'statistics:create', + STATISTICS_UPDATE: 'statistics:update', + STATISTICS_DELETE: 'statistics:delete', + STATISTICS_EXPORT: 'statistics:export', + + // 仪表板相关权限 + DASHBOARD_VIEW: 'dashboard:view', + DASHBOARD_CREATE: 'dashboard:create', + DASHBOARD_UPDATE: 'dashboard:update', + DASHBOARD_DELETE: 'dashboard:delete', + + // 报告相关权限 + REPORT_VIEW: 'report:view', + REPORT_CREATE: 'report:create', + REPORT_UPDATE: 'report:update', + REPORT_DELETE: 'report:delete', + REPORT_EXPORT: 'report:export', + + // 分析相关权限 + ANALYSIS_VIEW: 'analysis:view', + ANALYSIS_RUN: 'analysis:run', + ANALYSIS_EXPORT: 'analysis:export', + + // 管理相关权限 + ADMIN_VIEW: 'admin:view', + ADMIN_MANAGE: 'admin:manage', + ADMIN_SETTINGS: 'admin:settings' +} + +// 角色常量 +export const ROLES = { + ADMIN: 'admin', + MANAGER: 'manager', + ANALYST: 'analyst', + USER: 'user' +} + +// 角色权限映射 +export const ROLE_PERMISSIONS = { + [ROLES.ADMIN]: [ + PERMISSIONS.STATISTICS_VIEW, + PERMISSIONS.STATISTICS_CREATE, + PERMISSIONS.STATISTICS_UPDATE, + PERMISSIONS.STATISTICS_DELETE, + PERMISSIONS.STATISTICS_EXPORT, + PERMISSIONS.DASHBOARD_VIEW, + PERMISSIONS.DASHBOARD_CREATE, + PERMISSIONS.DASHBOARD_UPDATE, + PERMISSIONS.DASHBOARD_DELETE, + PERMISSIONS.REPORT_VIEW, + PERMISSIONS.REPORT_CREATE, + PERMISSIONS.REPORT_UPDATE, + PERMISSIONS.REPORT_DELETE, + PERMISSIONS.REPORT_EXPORT, + PERMISSIONS.ANALYSIS_VIEW, + PERMISSIONS.ANALYSIS_RUN, + PERMISSIONS.ANALYSIS_EXPORT, + PERMISSIONS.ADMIN_VIEW, + PERMISSIONS.ADMIN_MANAGE, + PERMISSIONS.ADMIN_SETTINGS + ], + [ROLES.MANAGER]: [ + PERMISSIONS.STATISTICS_VIEW, + PERMISSIONS.STATISTICS_EXPORT, + PERMISSIONS.DASHBOARD_VIEW, + PERMISSIONS.DASHBOARD_CREATE, + PERMISSIONS.DASHBOARD_UPDATE, + PERMISSIONS.REPORT_VIEW, + PERMISSIONS.REPORT_CREATE, + PERMISSIONS.REPORT_EXPORT, + PERMISSIONS.ANALYSIS_VIEW, + PERMISSIONS.ANALYSIS_RUN, + PERMISSIONS.ANALYSIS_EXPORT + ], + [ROLES.ANALYST]: [ + PERMISSIONS.STATISTICS_VIEW, + PERMISSIONS.STATISTICS_EXPORT, + PERMISSIONS.DASHBOARD_VIEW, + PERMISSIONS.REPORT_VIEW, + PERMISSIONS.REPORT_EXPORT, + PERMISSIONS.ANALYSIS_VIEW, + PERMISSIONS.ANALYSIS_RUN, + PERMISSIONS.ANALYSIS_EXPORT + ], + [ROLES.USER]: [ + PERMISSIONS.STATISTICS_VIEW, + PERMISSIONS.DASHBOARD_VIEW, + PERMISSIONS.REPORT_VIEW, + PERMISSIONS.ANALYSIS_VIEW + ] +} + +// 获取用户权限 +export const getUserPermissions = (userRole) => { + return ROLE_PERMISSIONS[userRole] || [] +} + +// 检查用户是否有特定权限 +export const hasUserPermission = (userRole, permission) => { + const userPermissions = getUserPermissions(userRole) + return userPermissions.includes(permission) +} + +// 检查用户是否有任意权限 +export const hasAnyPermission = (userRole, permissions) => { + const userPermissions = getUserPermissions(userRole) + return permissions.some(permission => userPermissions.includes(permission)) +} + +// 检查用户是否有所有权限 +export const hasAllPermissions = (userRole, permissions) => { + const userPermissions = getUserPermissions(userRole) + return permissions.every(permission => userPermissions.includes(permission)) +} + +export default { + permission, + role, + checkPermission, + PERMISSIONS, + ROLES, + ROLE_PERMISSIONS, + getUserPermissions, + hasUserPermission, + hasAnyPermission, + hasAllPermissions +} + diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..4a4519c --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,305 @@ +import router from '@/router' +import axios from 'axios' +import { ElMessage } from 'element-plus' + +// 创建axios实例 +const request = axios.create({ + baseURL: '/api/v1', + timeout: 60000, // 1分钟超时(用于调试页面) + headers: { + 'Content-Type': 'application/json', + } +}) + +// 简单的认证错误事件总线 +const authEventBus = { + listeners: [], + onAuthError(callback) { + this.listeners.push(callback) + }, + emitAuthError(message) { + this.listeners.forEach(callback => { + try { + callback(message) + } catch (error) { + console.error('认证错误处理回调执行失败:', error) + } + }) + } +} + +// 请求拦截器 +request.interceptors.request.use( + config => { + // 添加认证token + const token = localStorage.getItem('access_token') + const tokenType = localStorage.getItem('token_type') || 'Bearer' + if (token) { + config.headers.Authorization = `${tokenType} ${token}` + } + + // 添加请求ID用于追踪 + config.headers['X-Request-ID'] = Date.now().toString() + + return config + }, + error => { + console.error('请求拦截器错误:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +request.interceptors.response.use( + response => { + const { data, status, config } = response + + // 检查HTTP状态码 + if (status >= 200 && status < 300) { + // 检查是否是文件下载API(通过URL判断) + if (config.url && config.url.includes('/generate-and-download')) { + // 直接返回原始响应,让前端处理文件下载 + return response + } + + // 严格按照后端响应格式处理 + if (data.success === true) { + // 成功响应,返回data字段 + return { + success: true, + data: data.data, + message: data.message, + requestId: data.request_id, + timestamp: data.timestamp, + pagination: data.pagination, + meta: data.meta + } + } else if (data.success === false) { + // 业务逻辑错误 + const message = data.message || '请求失败' + // 检查是否有验证错误 + if (data.errors && typeof data.errors === 'object') { + // 处理验证错误 + const errorMessages = [] + Object.keys(data.errors).forEach(field => { + const fieldErrors = data.errors[field] + if (Array.isArray(fieldErrors)) { + // 将字段的所有错误信息添加到数组中 + errorMessages.push(...fieldErrors) + } else if (typeof fieldErrors === 'string') { + // 单个错误字符串 + errorMessages.push(fieldErrors) + } + }) + + // 显示所有验证错误 + if (errorMessages.length > 0) { + errorMessages.forEach(errorMsg => { + ElMessage.error(errorMsg) + }) + } else { + ElMessage.error(message) + } + } else { + // 普通业务错误 + ElMessage.error(message) + } + + return Promise.reject({ + type: 'business', + message, + errors: data.errors, + requestId: data.request_id, + timestamp: data.timestamp + }) + } else { + // 没有success字段,可能是直接返回数据 + return { + success: true, + data: data, + message: '请求成功' + } + } + } else { + // HTTP状态码错误 + const message = data?.message || `HTTP错误: ${status}` + ElMessage.error(message) + return Promise.reject({ + type: 'http', + status, + message, + data: data + }) + } + }, + error => { + console.error('响应拦截器错误:', error) + + // 网络错误 + if (!error.response) { + ElMessage.error('网络连接失败,请检查网络设置') + return Promise.reject({ + type: 'network', + message: '网络连接失败' + }) + } + + const { status, data } = error.response + + // 根据状态码处理不同类型的错误 + let errorMessage = '' + + switch (status) { + case 400: + // 请求参数错误 + errorMessage = data?.message || '请求参数错误' + ElMessage.error(errorMessage) + break + + case 401: + // 认证失败 - 使用事件总线通知 + errorMessage = data?.message || '登录已过期,请重新登录' + handleAuthError(errorMessage) + break + + case 403: + // 权限不足 + errorMessage = data?.message || '权限不足,无法访问此资源' + ElMessage.error(errorMessage) + break + + case 404: + // 资源不存在 + errorMessage = data?.message || '请求的资源不存在' + ElMessage.error(errorMessage) + break + + case 409: + // 资源冲突 + errorMessage = data?.message || '资源冲突,请检查输入信息' + ElMessage.error(errorMessage) + break + + case 422: + // 参数校验失败 + errorMessage = data?.message || '请求参数验证失败' + + // 检查是否有验证错误 + if (data?.errors && typeof data.errors === 'object') { + // 处理验证错误 + const errorMessages = [] + Object.keys(data.errors).forEach(field => { + const fieldErrors = data.errors[field] + if (Array.isArray(fieldErrors)) { + // 将字段的所有错误信息添加到数组中 + errorMessages.push(...fieldErrors) + } else if (typeof fieldErrors === 'string') { + // 单个错误字符串 + errorMessages.push(fieldErrors) + } + }) + + // 显示所有验证错误 + if (errorMessages.length > 0) { + errorMessages.forEach(errorMsg => { + ElMessage.error(errorMsg) + }) + } else { + ElMessage.error(errorMessage) + } + } else { + ElMessage.error(errorMessage) + } + break + + case 429: + // 请求频率限制 + errorMessage = data?.message || '请求过于频繁,请稍后再试' + ElMessage.error(errorMessage) + break + + case 500: + // 服务器内部错误 + errorMessage = data?.message || '服务器内部错误,请稍后再试' + ElMessage.error(errorMessage) + break + + default: + // 其他错误 + errorMessage = data?.message || `请求失败 (${status})` + ElMessage.error(errorMessage) + } + + return Promise.reject({ + type: 'http', + status, + message: data?.message || `HTTP错误: ${status}`, + data: data, + requestId: data?.request_id, + timestamp: data?.timestamp + }) + } +) + +// 处理认证错误的函数 +const handleAuthError = (message) => { + // 清理localStorage + localStorage.removeItem('access_token') + localStorage.removeItem('token_type') + + // 通过事件总线通知其他组件 + authEventBus.emitAuthError(message) + + // 显示错误消息 + ElMessage.error(message) + + // 跳转到登录页面(如果不在登录页面) + if (router.currentRoute.value.path !== '/auth/login') { + router.push('/auth/login') + } +} + +// 导出请求实例 +export default request + +// 导出认证事件总线 +export { authEventBus } + +// 导出响应处理工具函数 +export const handleResponse = (response) => { + if (response.success) { + return response.data + } + throw new Error(response.message || '请求失败') +} + +// 导出错误处理工具函数 +export const handleError = (error) => { + if (error.type === 'business') { + // 业务逻辑错误 + return { + success: false, + message: error.message, + errors: error.errors + } + } else if (error.type === 'http') { + // HTTP错误 + return { + success: false, + message: error.message, + status: error.status + } + } else if (error.type === 'network') { + // 网络错误 + return { + success: false, + message: error.message + } + } + + // 未知错误 + return { + success: false, + message: '未知错误' + } +} diff --git a/src/utils/smsSignature.js b/src/utils/smsSignature.js new file mode 100644 index 0000000..19aeb78 --- /dev/null +++ b/src/utils/smsSignature.js @@ -0,0 +1,212 @@ +/** + * 短信发送接口签名和编码工具 + * + * 用于生成HMAC-SHA256签名和自定义编码请求数据 + */ + +/** + * 自定义编码字符集(与后端保持一致) + */ +const CUSTOM_ENCODE_CHARSET = + '0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz!@#$%^&*()_+-=[]{}|;:,.<>?' + +/** + * 获取签名密钥(通过多种方式混淆,增加破解难度) + * 注意:这只是示例,实际使用时应该进一步混淆 + */ +function getSecretKey() { + // 方式1: 字符串拆分和拼接 + const part1 = 'HyApi2024' + const part2 = 'SMSSecret' + const part3 = 'Key!@#$%^' + const part4 = '&*()_+QWERTY' + const part5 = 'UIOP' + + // 方式2: 使用数组和join(增加混淆) + const arr = [part1, part2, part3, part4, part5] + return arr.join('') +} + +/** + * 生成随机字符串(用于nonce) + */ +export function generateNonce(length = 16) { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + let result = '' + const array = new Uint8Array(length) + crypto.getRandomValues(array) + for (let i = 0; i < length; i++) { + result += chars[array[i] % chars.length] + } + return result +} + +/** + * 使用Web Crypto API生成HMAC-SHA256签名 + * + * @param {Object} params - 请求参数对象 + * @param {string} secretKey - 签名密钥 + * @param {number} timestamp - 时间戳(秒) + * @param {string} nonce - 随机字符串 + * @returns {Promise} 签名字符串(hex编码) + */ +async function generateSignature(params, secretKey, timestamp, nonce) { + // 1. 构建待签名字符串:按key排序,拼接成 key1=value1&key2=value2 格式 + const keys = Object.keys(params) + .filter((k) => k !== 'signature') // 排除签名字段 + .sort() + + const parts = keys.map((k) => `${k}=${params[k]}`) + + // 2. 添加时间戳和随机数 + parts.push(`timestamp=${timestamp}`) + parts.push(`nonce=${nonce}`) + + // 3. 拼接成待签名字符串 + const signString = parts.join('&') + + // 4. 使用Web Crypto API计算HMAC-SHA256签名 + const encoder = new TextEncoder() + const keyData = encoder.encode(secretKey) + const messageData = encoder.encode(signString) + + // 导入密钥 + const cryptoKey = await crypto.subtle.importKey( + 'raw', + keyData, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'], + ) + + // 计算签名 + const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData) + + // 转换为hex字符串 + const hashArray = Array.from(new Uint8Array(signature)) + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') + + return hashHex +} + +/** + * 自定义Base64编码(使用自定义字符集) + */ +function customBase64Encode(data) { + if (data.length === 0) return '' + + // 将字符串转换为UTF-8字节数组 + const encoder = new TextEncoder() + const bytes = encoder.encode(data) + const charset = CUSTOM_ENCODE_CHARSET + let result = '' + + // 将3个字节(24位)编码为4个字符 + for (let i = 0; i < bytes.length; i += 3) { + const b1 = bytes[i] + const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0 + const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0 + + // 组合成24位 + const combined = (b1 << 16) | (b2 << 8) | b3 + + // 分成4个6位段 + result += charset[(combined >> 18) & 0x3f] + result += charset[(combined >> 12) & 0x3f] + + if (i + 1 < bytes.length) { + result += charset[(combined >> 6) & 0x3f] + } else { + result += '=' // 填充字符 + } + + if (i + 2 < bytes.length) { + result += charset[combined & 0x3f] + } else { + result += '=' // 填充字符 + } + } + + return result +} + +/** + * 应用字符偏移混淆 + */ +function applyCharShift(data, shift) { + const charset = CUSTOM_ENCODE_CHARSET + const charsetLen = charset.length + let result = '' + + for (let i = 0; i < data.length; i++) { + const c = data[i] + if (c === '=') { + result += c // 填充字符不变 + continue + } + + const idx = charset.indexOf(c) + if (idx === -1) { + result += c // 不在字符集中,保持不变 + } else { + // 应用偏移 + const newIdx = (idx + shift) % charsetLen + result += charset[newIdx] + } + } + + return result +} + +/** + * 自定义编码请求数据 + */ +export function encodeRequest(data) { + // 1. 使用自定义Base64编码 + const encoded = customBase64Encode(data) + + // 2. 应用字符偏移混淆(偏移7个位置) + const confused = applyCharShift(encoded, 7) + + return confused +} + +/** + * 生成并编码短信发送请求数据 + * + * @param {string} phone - 手机号 + * @param {string} scene - 场景(register/login/change_password/reset_password等) + * @returns {Promise<{data: string}>} 编码后的请求数据 + */ +export async function generateSMSRequest(phone, scene) { + // 1. 准备参数 + const timestamp = Math.floor(Date.now() / 1000) // 当前时间戳(秒) + const nonce = generateNonce(16) // 生成随机字符串 + + const params = { + phone: phone, + scene: scene, + } + + // 2. 生成签名 + const secretKey = getSecretKey() + const signature = await generateSignature(params, secretKey, timestamp, nonce) + + // 3. 构建包含所有参数的JSON对象 + const allParams = { + phone: phone, + scene: scene, + timestamp: timestamp, + nonce: nonce, + signature: signature, + } + + // 4. 将JSON对象转换为字符串,然后使用自定义编码方案编码 + const jsonString = JSON.stringify(allParams) + const encodedData = encodeRequest(jsonString) + + // 5. 返回编码后的数据 + return { + data: encodedData, + } +} diff --git a/src/utils/version.js b/src/utils/version.js new file mode 100644 index 0000000..e1fea58 --- /dev/null +++ b/src/utils/version.js @@ -0,0 +1,239 @@ +/** + * 版本管理工具 + * 用于管理应用版本和token版本,实现自动退出登录和页面刷新 + */ + +// 版本配置 +export const VERSION_CONFIG = { + // 当前token版本,修改JWT密钥时需要更新 + TOKEN_VERSION: '1.0.0', + + // 当前应用版本,前端更新时需要更新 + APP_VERSION: '1.0.0', + + // 版本检查间隔(毫秒) + CHECK_INTERVAL: 60000, // 60秒检查一次 + + // 是否启用自动版本检查 + AUTO_CHECK: true +} + +// 本地存储键名 +const STORAGE_KEYS = { + TOKEN_VERSION: 'token_version', + APP_VERSION: 'app_version', + LAST_CHECK_TIME: 'last_version_check_time' +} + +/** + * 获取本地存储的版本信息 + */ +export const getLocalVersions = () => { + return { + tokenVersion: localStorage.getItem(STORAGE_KEYS.TOKEN_VERSION) || '1.0.0', + appVersion: localStorage.getItem(STORAGE_KEYS.APP_VERSION) || '1.0.0', + lastCheckTime: localStorage.getItem(STORAGE_KEYS.LAST_CHECK_TIME) || '0' + } +} + +/** + * 保存版本信息到本地存储 + */ +export const saveLocalVersions = (versions) => { + if (versions.tokenVersion) { + localStorage.setItem(STORAGE_KEYS.TOKEN_VERSION, versions.tokenVersion) + } + if (versions.appVersion) { + localStorage.setItem(STORAGE_KEYS.APP_VERSION, versions.appVersion) + } + localStorage.setItem(STORAGE_KEYS.LAST_CHECK_TIME, Date.now().toString()) +} + +/** + * 清除版本信息 + */ +export const clearLocalVersions = () => { + localStorage.removeItem(STORAGE_KEYS.TOKEN_VERSION) + localStorage.removeItem(STORAGE_KEYS.APP_VERSION) + localStorage.removeItem(STORAGE_KEYS.LAST_CHECK_TIME) +} + +/** + * 比较版本号 + * @param {string} version1 版本1 + * @param {string} version2 版本2 + * @returns {number} 1: version1 > version2, -1: version1 < version2, 0: 相等 + */ +export const compareVersions = (version1, version2) => { + const v1Parts = version1.split('.').map(Number) + const v2Parts = version2.split('.').map(Number) + + const maxLength = Math.max(v1Parts.length, v2Parts.length) + + for (let i = 0; i < maxLength; i++) { + const v1 = v1Parts[i] || 0 + const v2 = v2Parts[i] || 0 + + if (v1 > v2) return 1 + if (v1 < v2) return -1 + } + + return 0 +} + +/** + * 检查是否需要更新 + */ +export const checkForUpdates = () => { + const localVersions = getLocalVersions() + const currentTime = Date.now() + const lastCheckTime = parseInt(localVersions.lastCheckTime) + + // 检查是否到了检查时间 + if (currentTime - lastCheckTime < VERSION_CONFIG.CHECK_INTERVAL) { + return { needsCheck: false } + } + + // 检查token版本 + const tokenVersionMatch = localVersions.tokenVersion === VERSION_CONFIG.TOKEN_VERSION + if (!tokenVersionMatch) { + return { + needsCheck: true, + needsLogout: true, + reason: 'token_version_mismatch', + current: localVersions.tokenVersion, + expected: VERSION_CONFIG.TOKEN_VERSION + } + } + + // 检查应用版本 + const appVersionMatch = localVersions.appVersion === VERSION_CONFIG.APP_VERSION + if (!appVersionMatch) { + return { + needsCheck: true, + needsRefresh: true, + reason: 'app_version_mismatch', + current: localVersions.appVersion, + expected: VERSION_CONFIG.APP_VERSION + } + } + + // 更新检查时间 + saveLocalVersions({}) + + return { needsCheck: true, upToDate: true } +} + +/** + * 版本检查器类 + */ +export class VersionChecker { + constructor() { + this.checkInterval = null + this.isChecking = false + } + + /** + * 开始自动版本检查 + */ + startAutoCheck() { + if (!VERSION_CONFIG.AUTO_CHECK || this.checkInterval) { + return + } + + this.checkInterval = setInterval(() => { + this.performCheck() + }, VERSION_CONFIG.CHECK_INTERVAL) + + console.log('版本自动检查已启动,检查间隔:', VERSION_CONFIG.CHECK_INTERVAL, 'ms') + } + + /** + * 停止自动版本检查 + */ + stopAutoCheck() { + if (this.checkInterval) { + clearInterval(this.checkInterval) + this.checkInterval = null + console.log('版本自动检查已停止') + } + } + + /** + * 执行版本检查 + */ + async performCheck() { + if (this.isChecking) { + return + } + + this.isChecking = true + + try { + const result = checkForUpdates() + + if (result.needsCheck && !result.upToDate) { + // 触发版本更新事件 + this.handleVersionUpdate(result) + } + } catch (error) { + console.error('版本检查失败:', error) + } finally { + this.isChecking = false + } + } + + /** + * 处理版本更新 + */ + handleVersionUpdate(result) { + if (result.needsLogout) { + console.warn('Token版本不匹配,需要重新登录:', result) + + // 触发退出登录事件 + const event = new CustomEvent('version:logout', { + detail: { + reason: result.reason, + current: result.current, + expected: result.expected + } + }) + window.dispatchEvent(event) + } + + if (result.needsRefresh) { + console.log('应用版本已更新,需要刷新页面:', result) + + // 触发页面刷新事件 + const event = new CustomEvent('version:refresh', { + detail: { + reason: result.reason, + current: result.current, + expected: result.expected + } + }) + window.dispatchEvent(event) + } + } + + /** + * 手动触发版本检查 + */ + async manualCheck() { + await this.performCheck() + } +} + +// 创建全局版本检查器实例 +export const versionChecker = new VersionChecker() + +// 导出默认配置 +export default { + VERSION_CONFIG, + getLocalVersions, + saveLocalVersions, + clearLocalVersions, + compareVersions, + checkForUpdates, + versionChecker +} diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue new file mode 100644 index 0000000..f6eb08b --- /dev/null +++ b/src/views/HomeView.vue @@ -0,0 +1,8 @@ + + + diff --git a/src/views/statistics/AnalysisView.vue b/src/views/statistics/AnalysisView.vue new file mode 100644 index 0000000..39fcc7d --- /dev/null +++ b/src/views/statistics/AnalysisView.vue @@ -0,0 +1,382 @@ + + + + + diff --git a/src/views/statistics/ReportsView.vue b/src/views/statistics/ReportsView.vue new file mode 100644 index 0000000..99f7106 --- /dev/null +++ b/src/views/statistics/ReportsView.vue @@ -0,0 +1,487 @@ + + + + + diff --git a/src/views/statistics/StatisticsPage.vue b/src/views/statistics/StatisticsPage.vue new file mode 100644 index 0000000..38472fa --- /dev/null +++ b/src/views/statistics/StatisticsPage.vue @@ -0,0 +1,946 @@ + + + + + diff --git a/src/views/statistics/UserStatisticsPage.vue b/src/views/statistics/UserStatisticsPage.vue new file mode 100644 index 0000000..d8ef737 --- /dev/null +++ b/src/views/statistics/UserStatisticsPage.vue @@ -0,0 +1,1505 @@ + + + + + diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..c7361b5 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,131 @@ +import { fileURLToPath, URL } from 'node:url' + +import tailwindcss from '@tailwindcss/vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import Components from 'unplugin-vue-components/vite' +import { defineConfig } from 'vite' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + tailwindcss(), + AutoImport({ + // 自动引入 Vue 相关函数 + imports: [ + 'vue', + 'vue-router', + 'pinia', + '@vueuse/core', + { + axios: [['default', 'axios']], + dayjs: [['default', 'dayjs']], + 'lodash-es': [ + 'isEmpty', + 'isEqual', + 'get', + 'set', + 'omit', + 'pick', + 'merge', + 'uniq', + 'uniqBy', + 'groupBy', + 'keyBy', + 'sortBy', + 'orderBy', + 'filter', + 'map', + 'reduce', + 'find', + 'findIndex', + 'includes', + 'startsWith', + 'endsWith', + 'camelCase', + 'kebabCase', + 'snakeCase', + 'capitalize', + 'upperCase', + 'lowerCase', + 'trim', + 'escape', + 'unescape', + ], + }, + ], + resolvers: [ElementPlusResolver()], + // 自动引入 Vue 模板中的组件 + dts: true, + // 自动引入目录下的模块 + dirs: ['./src/composables/**', './src/stores/**', './src/utils/**'], + // 自动引入 Vue 相关函数 + vueTemplate: true, + // 自动引入类型 + eslintrc: { + enabled: true, + filepath: './.eslintrc-auto-import.json', + globalsPropValue: true, + }, + }), + Components({ + // 自动引入组件 + resolvers: [ + ElementPlusResolver({ + importStyle: 'sass', + }), + ], + // 自动引入目录下的组件 + dirs: ['src/components'], + // 组件的有效文件扩展名 + extensions: ['vue'], + // 配置文件生成位置 + dts: true, + // 自动引入指令 + directives: true, + }), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + css: { + preprocessorOptions: { + scss: { + importers: [ + // ... + ], + }, + }, + }, + build: { + sourcemap: true, + rollupOptions: { + output: { + manualChunks: undefined, + }, + }, + }, + server: { + proxy: { + // // 本地开发时将 /api/v1 的请求代理到 8080 端口 + '/api/v1': { + target: 'http://localhost:8080', + changeOrigin: true, + // 保持路径不变 + rewrite: (path) => path, + }, + // '/api/v1': { + // target: 'https://console.haiyudata.com', + // changeOrigin: true, + // // 保持路径不变 + // rewrite: path => path, + // }, + }, + }, +})