From 7ec6bd789f938e06616d45cf9728f2ca72b25478 Mon Sep 17 00:00:00 2001 From: wangrui233 Date: Sun, 25 Oct 2026 23:33:42 +0800 Subject: [PATCH] init --- .eslintrc-auto-import.json | 313 +++++++++++++++++- pnpm-lock.yaml | 95 +----- src/App.vue | 15 + src/api/dashboard/index.ts | 11 + src/directives/permission/index.ts | 6 +- src/layouts/components/AppMain.vue | 49 +++ src/layouts/components/Navbar.vue | 111 +++++++ src/layouts/components/Sidebar/Logo.vue | 45 +++ .../components/Sidebar/SidebarItem.vue | 35 ++ src/layouts/components/Sidebar/index.vue | 65 ++++ src/layouts/index.vue | 55 +++ src/main.ts | 30 ++ src/router/index.ts | 6 +- src/styles/element/index.scss | 21 ++ src/styles/index.scss | 89 +++++ src/types/dashboard.ts | 13 + src/types/router.ts | 6 +- src/utils/request.ts | 52 +-- src/views/dashboard/index.vue | 129 ++++++++ src/views/error/404.vue | 36 ++ src/views/login/index.vue | 109 ++++++ src/views/system/user/components/UserForm.vue | 119 +++++++ src/views/system/user/index.vue | 173 ++++++++++ 23 files changed, 1473 insertions(+), 110 deletions(-) create mode 100644 src/App.vue create mode 100644 src/api/dashboard/index.ts create mode 100644 src/layouts/components/AppMain.vue create mode 100644 src/layouts/components/Navbar.vue create mode 100644 src/layouts/components/Sidebar/Logo.vue create mode 100644 src/layouts/components/Sidebar/SidebarItem.vue create mode 100644 src/layouts/components/Sidebar/index.vue create mode 100644 src/layouts/index.vue create mode 100644 src/main.ts create mode 100644 src/styles/element/index.scss create mode 100644 src/styles/index.scss create mode 100644 src/types/dashboard.ts create mode 100644 src/views/dashboard/index.vue create mode 100644 src/views/error/404.vue create mode 100644 src/views/login/index.vue create mode 100644 src/views/system/user/components/UserForm.vue create mode 100644 src/views/system/user/index.vue diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 87c4544..dbc1c16 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -1,3 +1,314 @@ { - "globals": {} + "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, + "DirectiveBinding": true, + "EffectScope": true, + "ExtractDefaultPropTypes": true, + "ExtractPropTypes": true, + "ExtractPublicPropTypes": true, + "InjectionKey": true, + "MaybeRef": true, + "MaybeRefOrGetter": true, + "PropType": true, + "Ref": true, + "VNode": true, + "WritableComputedRef": true, + "acceptHMRUpdate": true, + "asyncComputed": true, + "autoResetRef": true, + "computed": true, + "computedAsync": true, + "computedEager": true, + "computedInject": true, + "computedWithControl": true, + "controlledComputed": true, + "controlledRef": true, + "createApp": true, + "createEventHook": true, + "createGlobalState": true, + "createInjectionState": true, + "createPinia": true, + "createReactiveFn": true, + "createReusableTemplate": true, + "createSharedComposable": true, + "createTemplatePromise": true, + "createUnrefFn": true, + "customRef": true, + "debouncedRef": true, + "debouncedWatch": true, + "defineAsyncComponent": true, + "defineComponent": true, + "defineStore": true, + "eagerComputed": true, + "effectScope": true, + "extendRef": true, + "getActivePinia": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "ignorableWatch": true, + "inject": true, + "injectLocal": true, + "isDefined": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "makeDestructurable": true, + "mapActions": true, + "mapGetters": true, + "mapState": true, + "mapStores": true, + "mapWritableState": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeRouteLeave": true, + "onBeforeRouteUpdate": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onClickOutside": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onKeyStroke": true, + "onLongPress": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onStartTyping": true, + "onUnmounted": true, + "onUpdated": true, + "onWatcherCleanup": true, + "pausableWatch": true, + "provide": true, + "provideLocal": true, + "reactify": true, + "reactifyObject": true, + "reactive": true, + "reactiveComputed": true, + "reactiveOmit": true, + "reactivePick": true, + "readonly": true, + "ref": true, + "refAutoReset": true, + "refDebounced": true, + "refDefault": true, + "refThrottled": true, + "refWithControl": true, + "resolveComponent": true, + "resolveRef": true, + "resolveUnref": true, + "setActivePinia": true, + "setMapStoreSuffix": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "storeToRefs": true, + "syncRef": true, + "syncRefs": true, + "templateRef": true, + "throttledRef": true, + "throttledWatch": true, + "toRaw": true, + "toReactive": true, + "toRef": true, + "toRefs": true, + "toValue": true, + "triggerRef": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unref": true, + "unrefElement": true, + "until": true, + "useActiveElement": true, + "useAnimate": true, + "useArrayDifference": true, + "useArrayEvery": true, + "useArrayFilter": true, + "useArrayFind": true, + "useArrayFindIndex": true, + "useArrayFindLast": true, + "useArrayIncludes": true, + "useArrayJoin": true, + "useArrayMap": true, + "useArrayReduce": true, + "useArraySome": true, + "useArrayUnique": true, + "useAsyncQueue": true, + "useAsyncState": true, + "useAttrs": true, + "useBase64": true, + "useBattery": true, + "useBluetooth": true, + "useBreakpoints": true, + "useBroadcastChannel": true, + "useBrowserLocation": true, + "useCached": true, + "useClipboard": true, + "useClipboardItems": true, + "useCloned": true, + "useColorMode": true, + "useConfirmDialog": true, + "useCounter": true, + "useCssModule": true, + "useCssVar": true, + "useCssVars": true, + "useCurrentElement": true, + "useCycleList": true, + "useDark": true, + "useDateFormat": true, + "useDebounce": true, + "useDebounceFn": true, + "useDebouncedRefHistory": true, + "useDeviceMotion": true, + "useDeviceOrientation": true, + "useDevicePixelRatio": true, + "useDevicesList": true, + "useDisplayMedia": true, + "useDocumentVisibility": true, + "useDraggable": true, + "useDropZone": true, + "useElementBounding": true, + "useElementByPoint": true, + "useElementHover": true, + "useElementSize": true, + "useElementVisibility": true, + "useEventBus": true, + "useEventListener": true, + "useEventSource": true, + "useEyeDropper": true, + "useFavicon": true, + "useFetch": true, + "useFileDialog": true, + "useFileSystemAccess": true, + "useFocus": true, + "useFocusWithin": true, + "useFps": true, + "useFullscreen": true, + "useGamepad": true, + "useGeolocation": true, + "useId": true, + "useIdle": true, + "useImage": true, + "useInfiniteScroll": true, + "useIntersectionObserver": true, + "useInterval": true, + "useIntervalFn": true, + "useKeyModifier": true, + "useLastChanged": true, + "useLink": true, + "useLocalStorage": true, + "useMagicKeys": true, + "useManualRefHistory": true, + "useMediaControls": true, + "useMediaQuery": true, + "useMemoize": true, + "useMemory": true, + "useModel": true, + "useMounted": true, + "useMouse": true, + "useMouseInElement": true, + "useMousePressed": true, + "useMutationObserver": true, + "useNavigatorLanguage": true, + "useNetwork": true, + "useNow": true, + "useObjectUrl": true, + "useOffsetPagination": true, + "useOnline": true, + "usePageLeave": true, + "useParallax": true, + "useParentElement": true, + "usePerformanceObserver": true, + "usePermission": true, + "usePointer": true, + "usePointerLock": true, + "usePointerSwipe": true, + "usePreferredColorScheme": true, + "usePreferredContrast": true, + "usePreferredDark": true, + "usePreferredLanguages": true, + "usePreferredReducedMotion": true, + "usePrevious": true, + "useRafFn": true, + "useRefHistory": true, + "useResizeObserver": true, + "useRoute": true, + "useRouter": true, + "useScreenOrientation": true, + "useScreenSafeArea": true, + "useScriptTag": true, + "useScroll": true, + "useScrollLock": true, + "useSessionStorage": true, + "useShare": true, + "useSlots": true, + "useSorted": true, + "useSpeechRecognition": true, + "useSpeechSynthesis": true, + "useStepper": true, + "useStorage": true, + "useStorageAsync": true, + "useStyleTag": true, + "useSupported": true, + "useSwipe": true, + "useTemplateRef": true, + "useTemplateRefsList": true, + "useTextDirection": true, + "useTextSelection": true, + "useTextareaAutosize": true, + "useThrottle": true, + "useThrottleFn": true, + "useThrottledRefHistory": true, + "useTimeAgo": true, + "useTimeout": true, + "useTimeoutFn": true, + "useTimeoutPoll": true, + "useTimestamp": true, + "useTitle": true, + "useToNumber": true, + "useToString": true, + "useToggle": true, + "useTransition": true, + "useUrlSearchParams": true, + "useUserMedia": true, + "useVModel": true, + "useVModels": true, + "useVibrate": true, + "useVirtualList": true, + "useWakeLock": true, + "useWebNotification": true, + "useWebSocket": true, + "useWebWorker": true, + "useWebWorkerFn": true, + "useWindowFocus": true, + "useWindowScroll": true, + "useWindowSize": true, + "watch": true, + "watchArray": true, + "watchAtMost": true, + "watchDebounced": true, + "watchDeep": true, + "watchEffect": true, + "watchIgnorable": true, + "watchImmediate": true, + "watchOnce": true, + "watchPausable": true, + "watchPostEffect": true, + "watchSyncEffect": true, + "watchThrottled": true, + "watchTriggerable": true, + "watchWithFilter": true, + "whenever": true, + "ElMessage": true, + "ElMessageBox": true + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d87ee7..681b283 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: pinia: specifier: ^2.2.4 version: 2.3.1(typescript@5.5.4)(vue@3.5.44(typescript@5.5.4)) + pinia-plugin-persistedstate: + specifier: ^3.2.3 + version: 3.2.3(pinia@2.3.1(typescript@5.5.4)(vue@3.5.44(typescript@5.5.4))) vue: specifier: ^3.5.12 version: 3.5.44(typescript@5.5.4) @@ -51,9 +54,6 @@ importers: '@vitejs/plugin-vue': specifier: ^5.1.4 version: 5.2.4(vite@5.4.21(@types/node@22.20.5)(sass@1.106.1))(vue@3.5.44(typescript@5.5.4)) - autoprefixer: - specifier: ^10.4.20 - version: 10.6.1(postcss@8.5.31) eslint: specifier: ^8.57.1 version: 8.57.1 @@ -768,24 +768,12 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - autoprefixer@10.6.1: - resolution: {integrity: sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - axios@1.20.0: resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.11.32: - resolution: {integrity: sha512-UvuxFU9P3hM29LrQdwu0cD6w6/+SFr+UlB9s/knI7+O2rK9xFxKUwSDWxGdbbqQ9/V7BJ5s0NI5nQAE4uJp3gQ==} - engines: {node: '>=6.0.0'} - hasBin: true - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -803,11 +791,6 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.29.4: - resolution: {integrity: sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -816,9 +799,6 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001819: - resolution: {integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -920,9 +900,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.464: - resolution: {integrity: sha512-nRa65aFge40viPAKxMKULIY8tcXKwKboJWNn4RoKwyWXJ6SP7BnrDGKUTzc64818jIciH0PK7LSF8QzZes19GA==} - element-plus@2.14.7: resolution: {integrity: sha512-lrG7pZyCS+mpCNVpzxxyu5r35+T6V0hzkAeIwn7HA6to772XSTHKuYCZG51OfzlqaxGbNegLxs650zAF1j43gA==} peerDependencies: @@ -960,10 +937,6 @@ packages: engines: {node: '>=12'} 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'} @@ -1099,9 +1072,6 @@ packages: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1378,10 +1348,6 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-releases@2.0.60: - resolution: {integrity: sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==} - engines: {node: '>=18'} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1468,6 +1434,11 @@ packages: engines: {node: '>=0.10'} hasBin: true + pinia-plugin-persistedstate@3.2.3: + resolution: {integrity: sha512-Cm819WBj/s5K5DGw55EwbXDtx+EZzM0YR5AZbq9XE3u0xvXwvX2JnWoFpWIcdzISBHqy9H1UiSIUmXyXqWsQRQ==} + peerDependencies: + pinia: ^2.0.0 + pinia@2.3.1: resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==} peerDependencies: @@ -1487,9 +1458,6 @@ packages: resolution: {integrity: sha512-TEHFzb5KRDKaOgJ4TYE+0jyJtFbjLvkdVOu0fZ2b6MTWzoqZd4Le+61aEppWkvTlYePgOX4ATL1wsAIPMLTw6Q==} engines: {node: '>=4'} - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.31: resolution: {integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==} engines: {node: ^10 || ^12 || >=14} @@ -1697,12 +1665,6 @@ packages: resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} engines: {node: '>=14.0.0'} - update-browserslist-db@1.3.4: - resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2387,15 +2349,6 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.6.1(postcss@8.5.31): - dependencies: - browserslist: 4.29.4 - caniuse-lite: 1.0.30001819 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.31 - postcss-value-parser: 4.2.0 - axios@1.20.0: dependencies: follow-redirects: 1.16.1 @@ -2408,8 +2361,6 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.11.32: {} - binary-extensions@2.3.0: {} boolbase@1.0.0: {} @@ -2427,14 +2378,6 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.29.4: - dependencies: - baseline-browser-mapping: 2.11.32 - caniuse-lite: 1.0.30001819 - electron-to-chromium: 1.5.464 - node-releases: 2.0.60 - update-browserslist-db: 1.3.4(browserslist@4.29.4) - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -2442,8 +2385,6 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001819: {} - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2535,8 +2476,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.464: {} - element-plus@2.14.7(vue@3.5.44(typescript@5.5.4)): dependencies: '@ctrl/tinycolor': 4.2.1 @@ -2603,8 +2542,6 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - escalade@3.2.0: {} - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -2777,8 +2714,6 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 - fraction.js@5.3.4: {} - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -3044,8 +2979,6 @@ snapshots: node-addon-api@7.1.1: optional: true - node-releases@2.0.60: {} - normalize-path@3.0.0: {} normalize-wheel-es@1.2.0: {} @@ -3115,6 +3048,10 @@ snapshots: pidtree@0.6.1: {} + pinia-plugin-persistedstate@3.2.3(pinia@2.3.1(typescript@5.5.4)(vue@3.5.44(typescript@5.5.4))): + dependencies: + pinia: 2.3.1(typescript@5.5.4)(vue@3.5.44(typescript@5.5.4)) + pinia@2.3.1(typescript@5.5.4)(vue@3.5.44(typescript@5.5.4)): dependencies: '@vue/devtools-api': 6.6.4 @@ -3142,8 +3079,6 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-value-parser@4.2.0: {} - postcss@8.5.31: dependencies: nanoid: 3.3.20 @@ -3370,12 +3305,6 @@ snapshots: acorn: 8.19.0 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.3.4(browserslist@4.29.4): - dependencies: - browserslist: 4.29.4 - escalade: 3.2.0 - picocolors: 1.1.1 - uri-js@4.4.1: dependencies: punycode: 2.3.1 diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..bdd65cc --- /dev/null +++ b/src/App.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/api/dashboard/index.ts b/src/api/dashboard/index.ts new file mode 100644 index 0000000..204099a --- /dev/null +++ b/src/api/dashboard/index.ts @@ -0,0 +1,11 @@ +import request from '@/utils/request' +import type { DashboardSummary } from '@/types/dashboard' + +const BASE_URL = '/dashboard' + +export const DashboardAPI = { + /** 首页汇总数据 */ + summary() { + return request({ url: `${BASE_URL}/summary`, method: 'get' }) + }, +} diff --git a/src/directives/permission/index.ts b/src/directives/permission/index.ts index a97b699..3c2c1ae 100644 --- a/src/directives/permission/index.ts +++ b/src/directives/permission/index.ts @@ -10,7 +10,11 @@ function normalize(binding: DirectiveBinding): string[] { } /** 校验不通过时直接移除 DOM 节点,而非仅禁用 */ -function check(el: HTMLElement, binding: DirectiveBinding, checker: (code: string) => boolean) { +function check( + el: HTMLElement, + binding: DirectiveBinding, + checker: (code: string) => boolean, +) { const codes = normalize(binding) if (!codes.length) return diff --git a/src/layouts/components/AppMain.vue b/src/layouts/components/AppMain.vue new file mode 100644 index 0000000..7d5ef40 --- /dev/null +++ b/src/layouts/components/AppMain.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/layouts/components/Navbar.vue b/src/layouts/components/Navbar.vue new file mode 100644 index 0000000..f8e454c --- /dev/null +++ b/src/layouts/components/Navbar.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/src/layouts/components/Sidebar/Logo.vue b/src/layouts/components/Sidebar/Logo.vue new file mode 100644 index 0000000..2d757bc --- /dev/null +++ b/src/layouts/components/Sidebar/Logo.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/layouts/components/Sidebar/SidebarItem.vue b/src/layouts/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..f86d63a --- /dev/null +++ b/src/layouts/components/Sidebar/SidebarItem.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/layouts/components/Sidebar/index.vue b/src/layouts/components/Sidebar/index.vue new file mode 100644 index 0000000..c2d69ba --- /dev/null +++ b/src/layouts/components/Sidebar/index.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/layouts/index.vue b/src/layouts/index.vue new file mode 100644 index 0000000..af80c95 --- /dev/null +++ b/src/layouts/index.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..eca8a53 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,30 @@ +import { createApp } from 'vue' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import App from './App.vue' +import { setupDirectives } from '@/directives' +import router from '@/router' +import pinia from '@/store' +import '@/styles/index.scss' +import '@/styles/element/index.scss' + +// 以下组件为函数式调用,按需导入插件不会自动引入其样式 +import 'element-plus/theme-chalk/el-message.css' +import 'element-plus/theme-chalk/el-message-box.css' +import 'element-plus/theme-chalk/el-notification.css' +import 'element-plus/theme-chalk/el-loading.css' +import 'nprogress/nprogress.css' + +const app = createApp(App) + +// 全局注册图标,支持菜单按名称字符串渲染 +for (const [name, component] of Object.entries(ElementPlusIconsVue)) { + app.component(name, component) +} + +app.use(pinia) +app.use(router) +setupDirectives(app) + +void router.isReady().then(() => { + app.mount('#app') +}) diff --git a/src/router/index.ts b/src/router/index.ts index 6b2eadb..d8ca828 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -52,7 +52,11 @@ router.beforeEach(async (to, _from, next) => { // 已登录访问登录页 → 回首页 if (to.path === '/login') { - next(userStore.token ? { path: '/' } : undefined) + if (userStore.token) { + next({ path: '/' }) + } else { + next() + } return } diff --git a/src/styles/element/index.scss b/src/styles/element/index.scss new file mode 100644 index 0000000..b7e748b --- /dev/null +++ b/src/styles/element/index.scss @@ -0,0 +1,21 @@ +/** + * Element Plus 主题覆盖 + * 仅通过 CSS 变量覆盖,禁止使用 !important 打补丁 + */ +:root { + --el-color-primary: #{$color-primary}; + --el-font-size-base: 14px; + --el-border-radius-base: 4px; + --el-table-header-bg-color: #fafafa; + --el-table-header-text-color: #{$text-color-primary}; +} + +/* 顶部进度条 */ +#nprogress .bar { + background: $color-primary; +} + +/* 搜索区表单项间距 */ +.el-form--inline .el-form-item { + margin-right: 12px; +} diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..578be7f --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,89 @@ +* { + box-sizing: border-box; +} + +html, +body, +#app { + height: 100%; + margin: 0; + padding: 0; +} + +body { + font-family: + 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, + sans-serif; + font-size: 14px; + color: $text-color-primary; + background-color: $bg-page; + -webkit-font-smoothing: antialiased; +} + +a { + color: $color-primary; + text-decoration: none; +} + +/* 页面容器:所有业务页面根节点统一使用 */ +.app-container { + padding: $container-padding; +} + +/* 工具栏:搜索栏与操作按钮之间 */ +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 12px; +} + +/* 首屏 loading */ +.app-loading { + @include flex-center; + + height: 100%; + + &__spinner { + width: 36px; + height: 36px; + border: 3px solid $border-color; + border-top-color: $color-primary; + border-radius: 50%; + animation: app-loading-rotate 0.8s linear infinite; + } +} + +@keyframes app-loading-rotate { + to { + transform: rotate(360deg); + } +} + +/* 滚动条 */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(144, 147, 153, 0.3); + border-radius: 3px; +} + +/* 页面切换动画 */ +.fade-transform-enter-active, +.fade-transform-leave-active { + transition: all $transition-duration; +} + +.fade-transform-enter-from { + opacity: 0; + transform: translateX(-12px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(12px); +} diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts new file mode 100644 index 0000000..5662588 --- /dev/null +++ b/src/types/dashboard.ts @@ -0,0 +1,13 @@ +/** 仪表盘汇总数据 */ +export interface DashboardSummary { + userTotal: number + orderTotal: number + gmv: number + pendingApproval: number + trend: TrendPoint[] +} + +export interface TrendPoint { + date: string + value: number +} diff --git a/src/types/router.ts b/src/types/router.ts index b5a278d..6756d26 100644 --- a/src/types/router.ts +++ b/src/types/router.ts @@ -1,5 +1,7 @@ +import type { RouteMeta } from 'vue-router' + /** 路由元信息(与后端菜单字段保持一致) */ -export interface RouteMetaVO { +export interface RouteMetaVO extends RouteMeta { title?: string icon?: string hidden?: boolean @@ -7,6 +9,8 @@ export interface RouteMetaVO { keepAlive?: boolean /** 只有一个子路由时是否始终展示父级菜单 */ alwaysShow?: boolean + /** 高亮的菜单路径 */ + activeMenu?: string roles?: string[] } diff --git a/src/utils/request.ts b/src/utils/request.ts index 7c46ea2..6533100 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -1,9 +1,5 @@ -import axios, { - AxiosError, - type AxiosInstance, - type AxiosRequestConfig, - type AxiosResponse, -} from 'axios' +import type { AxiosError } from 'axios' +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios' import router from '@/router' import type { ApiResponse } from '@/types/common' import { getToken, removeToken } from '@/utils/storage' @@ -36,7 +32,7 @@ function handleUnauthorized(msg: string): void { }) } -// 请求拦截器 +// 请求拦截器:注入 Token service.interceptors.request.use((config) => { const token = getToken() if (token) { @@ -45,27 +41,20 @@ service.interceptors.request.use((config) => { return config }) -// 响应拦截器 +// 响应拦截器:仅处理登录态与网络层异常 service.interceptors.response.use( - (response: AxiosResponse): unknown => { - // 文件流直接透传,不做业务码判断 + (response: AxiosResponse) => { if (response.config.responseType === 'blob') { - return response.data - } - - const { code, data, msg } = response.data - - if (code === SUCCESS_CODE) { - return data + return response } + const { code, msg } = response.data if (code === TOKEN_INVALID_CODE) { handleUnauthorized(msg) - return Promise.reject(new Error(msg)) + return Promise.reject(new Error(msg || '登录状态已过期')) } - ElMessage.error(msg || '请求失败,请稍后重试') - return Promise.reject(new Error(msg || '请求失败')) + return response }, (error: AxiosError) => { const status = error.response?.status @@ -84,9 +73,26 @@ service.interceptors.response.use( ) /** - * 统一请求入口 - * @returns 直接返回业务数据(已在响应拦截器中剥离外层结构) + * 统一请求入口:剥离外层响应结构,直接返回业务数据 + * @param config 请求配置,responseType 为 blob 时直接返回文件流 */ export default function request(config: AxiosRequestConfig): Promise { - return service.request(config) + const { responseType, ...rest } = config + const finalConfig: AxiosRequestConfig = responseType ? { ...rest, responseType } : rest + + const pending = service(finalConfig) as unknown as Promise>> + + return pending.then((response) => { + if (responseType === 'blob') { + return response.data as T + } + + const { code, data, msg } = response.data + if (code === SUCCESS_CODE) { + return data + } + + ElMessage.error(msg || '请求失败,请稍后重试') + return Promise.reject(new Error(msg || '请求失败')) + }) } diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue new file mode 100644 index 0000000..de58aec --- /dev/null +++ b/src/views/dashboard/index.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/views/error/404.vue b/src/views/error/404.vue new file mode 100644 index 0000000..9f44ccc --- /dev/null +++ b/src/views/error/404.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..cbf4314 --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/views/system/user/components/UserForm.vue b/src/views/system/user/components/UserForm.vue new file mode 100644 index 0000000..6d355c5 --- /dev/null +++ b/src/views/system/user/components/UserForm.vue @@ -0,0 +1,119 @@ + + + diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..7ac01dd --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,173 @@ + + + + +