chore: init sysmgr-admin (Vue3 scaffold)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
9730b9fd5e
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@ -0,0 +1,8 @@
|
||||
[*.{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
|
||||
3
.env
Normal file
3
.env
Normal file
@ -0,0 +1,3 @@
|
||||
VITE_APP_NAME=demo_default
|
||||
VITE_NUMBER_DEMO=3000
|
||||
VITE_BOOLEAN_DEMO=true
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
# 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
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Cypress
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Vitest
|
||||
__screenshots__/
|
||||
|
||||
# Vite
|
||||
*.timestamp-*-*.mjs
|
||||
10
.oxlintrc.json
Normal file
10
.oxlintrc.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"categories": {
|
||||
"correctness": "error"
|
||||
}
|
||||
}
|
||||
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
||||
9
.vscode/extensions.json
vendored
Executable file
9
.vscode/extensions.json
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"oxc.oxc-vscode",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
67
README.md
Normal file
67
README.md
Normal file
@ -0,0 +1,67 @@
|
||||
# sysmgr-admin Vue3 全家桶通用模板项目
|
||||
|
||||
- 程序员优雅哥 youyacoder agarwood 沉香系列。
|
||||
- 整合了 TypeScript、Vue Router、Pinia、UnoCSS、SCSS、ESLint、Prettier、unplugin-vue-router、vite-plugin-vue-layouts、unplugin-auto-import、unplugin-vue-components、vueuse、vue-i18n。
|
||||
- 支持多环境配置:dev、uat、prod。
|
||||
- 最简代码模版,开箱即用,支持自动路由、全局布局、自动导入、组件自动注册。
|
||||
|
||||
## 分支说明
|
||||
- main: 最简模板代码
|
||||
- axios:集成 Axios,封装了网络请求
|
||||
|
||||
## 基于此模板创建新项目
|
||||
可以通用 -b 指定要拉取的分支
|
||||
|
||||
1)拉取最简模板(main 分支)
|
||||
```shell
|
||||
git clone git@github.com:HeroCloudy/agarwood-vue3-app-template.git --depth 1 -b main your-project-name
|
||||
```
|
||||
|
||||
2)拉取集成axios模板(axios 分支)
|
||||
```shell
|
||||
git clone git@github.com:HeroCloudy/agarwood-vue3-app-template.git --depth 1 -b axios your-project-name
|
||||
```
|
||||
|
||||
3)拉取集成 Element Plus 的模板(element-plus 分支)
|
||||
git clone git@github.com:HeroCloudy/wumeng-vue3-app-template.git --depth 1 -b element-plus your-project-name
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
```
|
||||
318
auto-imports.d.ts
vendored
Normal file
318
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
/* 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 acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
||||
const asyncComputed: typeof import('@vueuse/core').asyncComputed
|
||||
const autoResetRef: typeof import('@vueuse/core').autoResetRef
|
||||
const computed: typeof import('vue').computed
|
||||
const computedAsync: typeof import('@vueuse/core').computedAsync
|
||||
const computedEager: typeof import('@vueuse/core').computedEager
|
||||
const computedInject: typeof import('@vueuse/core').computedInject
|
||||
const computedWithControl: typeof import('@vueuse/core').computedWithControl
|
||||
const controlledComputed: typeof import('@vueuse/core').controlledComputed
|
||||
const controlledRef: typeof import('@vueuse/core').controlledRef
|
||||
const createApp: typeof import('vue').createApp
|
||||
const createDisposableDirective: typeof import('@vueuse/core').createDisposableDirective
|
||||
const createEventHook: typeof import('@vueuse/core').createEventHook
|
||||
const createGlobalState: typeof import('@vueuse/core').createGlobalState
|
||||
const createInjectionState: typeof import('@vueuse/core').createInjectionState
|
||||
const createPinia: typeof import('pinia').createPinia
|
||||
const createReactiveFn: typeof import('@vueuse/core').createReactiveFn
|
||||
const 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 debouncedRef: typeof import('@vueuse/core').debouncedRef
|
||||
const debouncedWatch: typeof import('@vueuse/core').debouncedWatch
|
||||
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
|
||||
const defineComponent: typeof import('vue').defineComponent
|
||||
const defineStore: typeof import('pinia').defineStore
|
||||
const eagerComputed: typeof import('@vueuse/core').eagerComputed
|
||||
const effectScope: typeof import('vue').effectScope
|
||||
const extendRef: typeof import('@vueuse/core').extendRef
|
||||
const getActivePinia: typeof import('pinia').getActivePinia
|
||||
const getCurrentInstance: typeof import('vue').getCurrentInstance
|
||||
const getCurrentScope: typeof import('vue').getCurrentScope
|
||||
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
||||
const h: typeof import('vue').h
|
||||
const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
|
||||
const inject: typeof import('vue').inject
|
||||
const injectLocal: typeof import('@vueuse/core').injectLocal
|
||||
const isDefined: typeof import('@vueuse/core').isDefined
|
||||
const isProxy: typeof import('vue').isProxy
|
||||
const isReactive: typeof import('vue').isReactive
|
||||
const isReadonly: typeof import('vue').isReadonly
|
||||
const isRef: typeof import('vue').isRef
|
||||
const isShallow: typeof import('vue').isShallow
|
||||
const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
|
||||
const mapActions: typeof import('pinia').mapActions
|
||||
const mapGetters: typeof import('pinia').mapGetters
|
||||
const mapState: typeof import('pinia').mapState
|
||||
const mapStores: typeof import('pinia').mapStores
|
||||
const mapWritableState: typeof import('pinia').mapWritableState
|
||||
const markRaw: typeof import('vue').markRaw
|
||||
const nextTick: typeof import('vue').nextTick
|
||||
const onActivated: typeof import('vue').onActivated
|
||||
const onBeforeMount: typeof import('vue').onBeforeMount
|
||||
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
|
||||
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
|
||||
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
|
||||
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
|
||||
const onClickOutside: typeof import('@vueuse/core').onClickOutside
|
||||
const onDeactivated: typeof import('vue').onDeactivated
|
||||
const 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 pausableWatch: typeof import('@vueuse/core').pausableWatch
|
||||
const provide: typeof import('vue').provide
|
||||
const provideLocal: typeof import('@vueuse/core').provideLocal
|
||||
const reactify: typeof import('@vueuse/core').reactify
|
||||
const reactifyObject: typeof import('@vueuse/core').reactifyObject
|
||||
const reactive: typeof import('vue').reactive
|
||||
const reactiveComputed: typeof import('@vueuse/core').reactiveComputed
|
||||
const reactiveOmit: typeof import('@vueuse/core').reactiveOmit
|
||||
const reactivePick: typeof import('@vueuse/core').reactivePick
|
||||
const readonly: typeof import('vue').readonly
|
||||
const ref: typeof import('vue').ref
|
||||
const refAutoReset: typeof import('@vueuse/core').refAutoReset
|
||||
const refDebounced: typeof import('@vueuse/core').refDebounced
|
||||
const refDefault: typeof import('@vueuse/core').refDefault
|
||||
const refManualReset: typeof import('@vueuse/core').refManualReset
|
||||
const refThrottled: typeof import('@vueuse/core').refThrottled
|
||||
const refWithControl: typeof import('@vueuse/core').refWithControl
|
||||
const resolveComponent: typeof import('vue').resolveComponent
|
||||
const resolveRef: typeof import('@vueuse/core').resolveRef
|
||||
const setActivePinia: typeof import('pinia').setActivePinia
|
||||
const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
|
||||
const shallowReactive: typeof import('vue').shallowReactive
|
||||
const shallowReadonly: typeof import('vue').shallowReadonly
|
||||
const shallowRef: typeof import('vue').shallowRef
|
||||
const storeToRefs: typeof import('pinia').storeToRefs
|
||||
const syncRef: typeof import('@vueuse/core').syncRef
|
||||
const syncRefs: typeof import('@vueuse/core').syncRefs
|
||||
const templateRef: typeof import('@vueuse/core').templateRef
|
||||
const throttledRef: typeof import('@vueuse/core').throttledRef
|
||||
const throttledWatch: typeof import('@vueuse/core').throttledWatch
|
||||
const toRaw: typeof import('vue').toRaw
|
||||
const toReactive: typeof import('@vueuse/core').toReactive
|
||||
const toRef: typeof import('vue').toRef
|
||||
const toRefs: typeof import('vue').toRefs
|
||||
const toValue: typeof import('vue').toValue
|
||||
const triggerRef: typeof import('vue').triggerRef
|
||||
const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount
|
||||
const tryOnMounted: typeof import('@vueuse/core').tryOnMounted
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose
|
||||
const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted
|
||||
const unref: typeof import('vue').unref
|
||||
const unrefElement: typeof import('@vueuse/core').unrefElement
|
||||
const until: typeof import('@vueuse/core').until
|
||||
const useActiveElement: typeof import('@vueuse/core').useActiveElement
|
||||
const useAnimate: typeof import('@vueuse/core').useAnimate
|
||||
const useArrayDifference: typeof import('@vueuse/core').useArrayDifference
|
||||
const useArrayEvery: typeof import('@vueuse/core').useArrayEvery
|
||||
const useArrayFilter: typeof import('@vueuse/core').useArrayFilter
|
||||
const useArrayFind: typeof import('@vueuse/core').useArrayFind
|
||||
const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex
|
||||
const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast
|
||||
const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes
|
||||
const useArrayJoin: typeof import('@vueuse/core').useArrayJoin
|
||||
const useArrayMap: typeof import('@vueuse/core').useArrayMap
|
||||
const useArrayReduce: typeof import('@vueuse/core').useArrayReduce
|
||||
const useArraySome: typeof import('@vueuse/core').useArraySome
|
||||
const useArrayUnique: typeof import('@vueuse/core').useArrayUnique
|
||||
const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue
|
||||
const useAsyncState: typeof import('@vueuse/core').useAsyncState
|
||||
const useAttrs: typeof import('vue').useAttrs
|
||||
const useBase64: typeof import('@vueuse/core').useBase64
|
||||
const useBattery: typeof import('@vueuse/core').useBattery
|
||||
const useBluetooth: typeof import('@vueuse/core').useBluetooth
|
||||
const useBreakpoints: typeof import('@vueuse/core').useBreakpoints
|
||||
const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel
|
||||
const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation
|
||||
const useCached: typeof import('@vueuse/core').useCached
|
||||
const useClipboard: typeof import('@vueuse/core').useClipboard
|
||||
const useClipboardItems: typeof import('@vueuse/core').useClipboardItems
|
||||
const useCloned: typeof import('@vueuse/core').useCloned
|
||||
const useColorMode: typeof import('@vueuse/core').useColorMode
|
||||
const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
|
||||
const useCountdown: typeof import('@vueuse/core').useCountdown
|
||||
const useCounter: typeof import('@vueuse/core').useCounter
|
||||
const useCssModule: typeof import('vue').useCssModule
|
||||
const useCssSupports: typeof import('@vueuse/core').useCssSupports
|
||||
const useCssVar: typeof import('@vueuse/core').useCssVar
|
||||
const useCssVars: typeof import('vue').useCssVars
|
||||
const useCurrentElement: typeof import('@vueuse/core').useCurrentElement
|
||||
const useCycleList: typeof import('@vueuse/core').useCycleList
|
||||
const useDark: typeof import('@vueuse/core').useDark
|
||||
const useDateFormat: typeof import('@vueuse/core').useDateFormat
|
||||
const useDebounce: typeof import('@vueuse/core').useDebounce
|
||||
const useDebounceFn: typeof import('@vueuse/core').useDebounceFn
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory
|
||||
const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion
|
||||
const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio
|
||||
const useDevicesList: typeof import('@vueuse/core').useDevicesList
|
||||
const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia
|
||||
const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility
|
||||
const useDraggable: typeof import('@vueuse/core').useDraggable
|
||||
const useDropZone: typeof import('@vueuse/core').useDropZone
|
||||
const useElementBounding: typeof import('@vueuse/core').useElementBounding
|
||||
const useElementByPoint: typeof import('@vueuse/core').useElementByPoint
|
||||
const useElementHover: typeof import('@vueuse/core').useElementHover
|
||||
const useElementSize: typeof import('@vueuse/core').useElementSize
|
||||
const useElementVisibility: typeof import('@vueuse/core').useElementVisibility
|
||||
const useEventBus: typeof import('@vueuse/core').useEventBus
|
||||
const useEventListener: typeof import('@vueuse/core').useEventListener
|
||||
const useEventSource: typeof import('@vueuse/core').useEventSource
|
||||
const useEyeDropper: typeof import('@vueuse/core').useEyeDropper
|
||||
const useFavicon: typeof import('@vueuse/core').useFavicon
|
||||
const useFetch: typeof import('@vueuse/core').useFetch
|
||||
const useFileDialog: typeof import('@vueuse/core').useFileDialog
|
||||
const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess
|
||||
const useFocus: typeof import('@vueuse/core').useFocus
|
||||
const useFocusWithin: typeof import('@vueuse/core').useFocusWithin
|
||||
const useFps: typeof import('@vueuse/core').useFps
|
||||
const useFullscreen: typeof import('@vueuse/core').useFullscreen
|
||||
const useGamepad: typeof import('@vueuse/core').useGamepad
|
||||
const useGeolocation: typeof import('@vueuse/core').useGeolocation
|
||||
const useId: typeof import('vue').useId
|
||||
const useIdle: typeof import('@vueuse/core').useIdle
|
||||
const useImage: typeof import('@vueuse/core').useImage
|
||||
const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll
|
||||
const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver
|
||||
const useInterval: typeof import('@vueuse/core').useInterval
|
||||
const useIntervalFn: typeof import('@vueuse/core').useIntervalFn
|
||||
const useKeyModifier: typeof import('@vueuse/core').useKeyModifier
|
||||
const useLastChanged: typeof import('@vueuse/core').useLastChanged
|
||||
const useLocalStorage: typeof import('@vueuse/core').useLocalStorage
|
||||
const useMagicKeys: typeof import('@vueuse/core').useMagicKeys
|
||||
const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory
|
||||
const useMediaControls: typeof import('@vueuse/core').useMediaControls
|
||||
const useMediaQuery: typeof import('@vueuse/core').useMediaQuery
|
||||
const useMemoize: typeof import('@vueuse/core').useMemoize
|
||||
const useMemory: typeof import('@vueuse/core').useMemory
|
||||
const useModel: typeof import('vue').useModel
|
||||
const useMounted: typeof import('@vueuse/core').useMounted
|
||||
const useMouse: typeof import('@vueuse/core').useMouse
|
||||
const useMouseInElement: typeof import('@vueuse/core').useMouseInElement
|
||||
const useMousePressed: typeof import('@vueuse/core').useMousePressed
|
||||
const useMutationObserver: typeof import('@vueuse/core').useMutationObserver
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage
|
||||
const useNetwork: typeof import('@vueuse/core').useNetwork
|
||||
const useNow: typeof import('@vueuse/core').useNow
|
||||
const useObjectUrl: typeof import('@vueuse/core').useObjectUrl
|
||||
const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination
|
||||
const useOnline: typeof import('@vueuse/core').useOnline
|
||||
const usePageLeave: typeof import('@vueuse/core').usePageLeave
|
||||
const useParallax: typeof import('@vueuse/core').useParallax
|
||||
const useParentElement: typeof import('@vueuse/core').useParentElement
|
||||
const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver
|
||||
const usePermission: typeof import('@vueuse/core').usePermission
|
||||
const usePointer: typeof import('@vueuse/core').usePointer
|
||||
const usePointerLock: typeof import('@vueuse/core').usePointerLock
|
||||
const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme
|
||||
const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast
|
||||
const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
|
||||
const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages
|
||||
const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion
|
||||
const 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 useSlots: typeof import('vue').useSlots
|
||||
const useSorted: typeof import('@vueuse/core').useSorted
|
||||
const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis
|
||||
const useStepper: typeof import('@vueuse/core').useStepper
|
||||
const useStorage: typeof import('@vueuse/core').useStorage
|
||||
const useStorageAsync: typeof import('@vueuse/core').useStorageAsync
|
||||
const useStyleTag: typeof import('@vueuse/core').useStyleTag
|
||||
const useSupported: typeof import('@vueuse/core').useSupported
|
||||
const useSwipe: typeof import('@vueuse/core').useSwipe
|
||||
const useTemplateRef: typeof import('vue').useTemplateRef
|
||||
const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList
|
||||
const useTextDirection: typeof import('@vueuse/core').useTextDirection
|
||||
const useTextSelection: typeof import('@vueuse/core').useTextSelection
|
||||
const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
|
||||
const useThrottle: typeof import('@vueuse/core').useThrottle
|
||||
const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
|
||||
const useTimeAgo: typeof import('@vueuse/core').useTimeAgo
|
||||
const 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 useTransition: typeof import('@vueuse/core').useTransition
|
||||
const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
|
||||
const useUserMedia: typeof import('@vueuse/core').useUserMedia
|
||||
const useVModel: typeof import('@vueuse/core').useVModel
|
||||
const useVModels: typeof import('@vueuse/core').useVModels
|
||||
const useVibrate: typeof import('@vueuse/core').useVibrate
|
||||
const useVirtualList: typeof import('@vueuse/core').useVirtualList
|
||||
const useWakeLock: typeof import('@vueuse/core').useWakeLock
|
||||
const useWebNotification: typeof import('@vueuse/core').useWebNotification
|
||||
const useWebSocket: typeof import('@vueuse/core').useWebSocket
|
||||
const useWebWorker: typeof import('@vueuse/core').useWebWorker
|
||||
const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn
|
||||
const useWindowFocus: typeof import('@vueuse/core').useWindowFocus
|
||||
const useWindowScroll: typeof import('@vueuse/core').useWindowScroll
|
||||
const useWindowSize: typeof import('@vueuse/core').useWindowSize
|
||||
const watch: typeof import('vue').watch
|
||||
const watchArray: typeof import('@vueuse/core').watchArray
|
||||
const watchAtMost: typeof import('@vueuse/core').watchAtMost
|
||||
const watchDebounced: typeof import('@vueuse/core').watchDebounced
|
||||
const watchDeep: typeof import('@vueuse/core').watchDeep
|
||||
const watchEffect: typeof import('vue').watchEffect
|
||||
const watchIgnorable: typeof import('@vueuse/core').watchIgnorable
|
||||
const watchImmediate: typeof import('@vueuse/core').watchImmediate
|
||||
const watchOnce: typeof import('@vueuse/core').watchOnce
|
||||
const watchPausable: typeof import('@vueuse/core').watchPausable
|
||||
const watchPostEffect: typeof import('vue').watchPostEffect
|
||||
const watchSyncEffect: typeof import('vue').watchSyncEffect
|
||||
const watchThrottled: typeof import('@vueuse/core').watchThrottled
|
||||
const watchTriggerable: typeof import('@vueuse/core').watchTriggerable
|
||||
const watchWithFilter: typeof import('@vueuse/core').watchWithFilter
|
||||
const whenever: typeof import('@vueuse/core').whenever
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
26
components.d.ts
vendored
Normal file
26
components.d.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// biome-ignore lint: disable
|
||||
// oxlint-disable
|
||||
// ------
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import { GlobalComponents } from 'vue'
|
||||
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Icon: typeof import('./src/components/icon/icon.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
}
|
||||
|
||||
// For TSX support
|
||||
declare global {
|
||||
const Icon: typeof import('./src/components/icon/icon.vue')['default']
|
||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||
const RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
23
env.d.ts
vendored
Executable file
23
env.d.ts
vendored
Executable file
@ -0,0 +1,23 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="unplugin-vue-router/client" />
|
||||
/// <reference types="vite-plugin-vue-layouts/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_NAME: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
readonly VITE_NUMBER_DEMO: number
|
||||
readonly VITE_BOOLEAN_DEMO: boolean
|
||||
}
|
||||
|
||||
declare module 'virtual:svg-icons-register' {
|
||||
const component: any
|
||||
export default component
|
||||
}
|
||||
|
||||
declare module 'virtual:svg-icons-names' {
|
||||
const iconsNames: string[]
|
||||
export default iconsNames
|
||||
}
|
||||
33
eslint.config.ts
Normal file
33
eslint.config.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import pluginOxlint from 'eslint-plugin-oxlint'
|
||||
import skipFormatting from 'eslint-config-prettier/flat'
|
||||
|
||||
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||
|
||||
export default defineConfigWithVueTs(
|
||||
{
|
||||
name: 'app/files-to-lint',
|
||||
files: ['**/*.{vue,ts,mts,tsx}'],
|
||||
},
|
||||
|
||||
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||
|
||||
...pluginVue.configs['flat/essential'],
|
||||
vueTsConfigs.recommended,
|
||||
|
||||
...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'),
|
||||
|
||||
skipFormatting,
|
||||
|
||||
{
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
)
|
||||
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
75
package.json
Normal file
75
package.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "sysmgr-admin",
|
||||
"version": "0.0.0",
|
||||
"packageManager": "pnpm@12.10.1",
|
||||
"description": "程序员优雅哥 agarwood 系列 vue3 模板项目",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:dev": "vite --mode dev",
|
||||
"dev:uat": "vite --mode uat",
|
||||
"build:dev": "run-p type-check \"build-only {@} --mode dev\" --",
|
||||
"build:uat": "run-p type-check \"build-only {@} --mode uat\" --",
|
||||
"build:prod": "run-p type-check \"build-only {@} --mode prod\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "run-s lint:*",
|
||||
"lint:oxlint": "oxlint . --fix",
|
||||
"lint:eslint": "eslint . --fix --cache",
|
||||
"format": "prettier --write --experimental-cli src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^5.0.1",
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"vue": "^3.5.38",
|
||||
"vue-i18n": "^11.4.6",
|
||||
"vue-router": "^5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.487",
|
||||
"@intlify/unplugin-vue-i18n": "^11.2.4",
|
||||
"@tsconfig/node24": "^24.0.4",
|
||||
"@types/node": "^24.12.2",
|
||||
"@vitejs/plugin-legacy": "^7.2.1",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||
"@vue/eslint-config-typescript": "^14.7.0",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"eslint": "^10.2.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-oxlint": "~1.60.0",
|
||||
"eslint-plugin-vue": "~10.8.0",
|
||||
"jiti": "^2.6.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"oxlint": "~1.60.0",
|
||||
"prettier": "3.8.4",
|
||||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"sass": "^1.101.0",
|
||||
"terser": "^5.48.0",
|
||||
"typescript": "~6.0.0",
|
||||
"unocss": "^66.7.2",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"unplugin-vue-router": "^0.19.2",
|
||||
"vite": "^8.0.16",
|
||||
"vite-plugin-compression2": "^2.5.3",
|
||||
"vite-plugin-image-optimizer": "^2.0.3",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-devtools": "^8.1.3",
|
||||
"vite-plugin-vue-layouts": "^0.11.0",
|
||||
"vue-tsc": "^3.2.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.23.1"
|
||||
},
|
||||
"keywords": [
|
||||
"vue3"
|
||||
],
|
||||
"author": {
|
||||
"name": "程序员优雅哥 youyacoder",
|
||||
"email": "hero_cloudy@126.com"
|
||||
}
|
||||
}
|
||||
8377
pnpm-lock.yaml
generated
Normal file
8377
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
allowBuilds:
|
||||
'@parcel/watcher': false
|
||||
core-js: false
|
||||
esbuild: false
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
5
src/App.vue
Normal file
5
src/App.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
<script setup lang="ts"></script>
|
||||
<style scoped></style>
|
||||
8
src/assets/icons/demo.svg
Normal file
8
src/assets/icons/demo.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M329.6 382.933333C323.2 389.333333 320 395.733333 320 405.333333s3.2 16 9.6 22.4 12.8 9.6 22.4 9.6 16-3.2 22.4-9.6c6.4-6.4 9.6-16 9.6-22.4s-3.2-16-9.6-22.4c-12.8-12.8-32-12.8-44.8 0zM649.6 382.933333C643.2 389.333333 640 395.733333 640 405.333333s3.2 16 9.6 22.4 12.8 9.6 22.4 9.6 16-3.2 22.4-9.6c6.4-6.4 9.6-16 9.6-22.4s-3.2-16-9.6-22.4c-12.8-12.8-32-12.8-44.8 0z"/>
|
||||
<path d="M512 938.666667c-235.264 0-426.666667-191.402667-426.666667-426.666667s191.402667-426.666667 426.666667-426.666667 426.666667 191.402667 426.666667 426.666667-191.402667 426.666667-426.666667 426.666667z m0-789.333334C312.021333 149.333333 149.333333 312.021333 149.333333 512c0 199.978667 162.688 362.666667 362.666667 362.666667 199.978667 0 362.666667-162.688 362.666667-362.666667 0-199.978667-162.688-362.666667-362.666667-362.666667z"/>
|
||||
<path d="M512 768c-99.989333 0-181.333333-81.344-181.333333-181.333333a32 32 0 1 1 64 0c0 64.693333 52.64 117.333333 117.333333 117.333333s117.333333-52.64 117.333333-117.333333a32 32 0 1 1 64 0c0 99.989333-81.344 181.333333-181.333333 181.333333z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
6
src/assets/scss/base/index.scss
Normal file
6
src/assets/scss/base/index.scss
Normal file
@ -0,0 +1,6 @@
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: var(--wm-bg-color-page);
|
||||
color: var(--wm-color-text-primary);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
3
src/assets/scss/index.scss
Normal file
3
src/assets/scss/index.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// 导入变量文件
|
||||
@use "./settings";
|
||||
@use "./base";
|
||||
73
src/assets/scss/settings/_color.scss
Normal file
73
src/assets/scss/settings/_color.scss
Normal file
@ -0,0 +1,73 @@
|
||||
@use "sass:color";
|
||||
|
||||
// 品牌与状态色
|
||||
$wm-color-primary: #1C9399;
|
||||
$wm-color-success: #3DBE7D;
|
||||
$wm-color-warning: #FFB74D;
|
||||
$wm-color-danger: #F1524C;
|
||||
$wm-color-info: #5E7CE0;
|
||||
|
||||
// 中性色 - 文字
|
||||
$wm-color-text-primary: #303133;
|
||||
$wm-color-text-regular: #606266;
|
||||
$wm-color-text-secondary: #909399;
|
||||
$wm-color-text-placeholder: #A8ABB2;
|
||||
$wm-color-text-disabled: #C0C4CC;
|
||||
|
||||
// 中性色 - 边框
|
||||
$wm-border-color-base: #DCDFE6;
|
||||
$wm-border-color-light: #E4E7ED;
|
||||
|
||||
// 中性色 - 背景
|
||||
$wm-bg-color-page: #F5F7FA;
|
||||
$wm-bg-color-base: #FFFFFF;
|
||||
|
||||
// 默认主题 白天模式
|
||||
:root {
|
||||
// 品牌/状态色及其色阶
|
||||
@each $type, $color in (primary, $wm-color-primary),
|
||||
(success, $wm-color-success),
|
||||
(danger, $wm-color-danger),
|
||||
(warning, $wm-color-warning),
|
||||
(info, $wm-color-info) {
|
||||
|
||||
--wm-color-#{$type}: #{$color};
|
||||
|
||||
// 生成浅色变体 (混合白色)
|
||||
@each $i in (1, 2, 3, 5, 7, 8, 9) {
|
||||
--wm-color-#{$type}-light-#{$i}: #{color.mix(#fff, $color, $i * 10%)};
|
||||
}
|
||||
|
||||
// 生成深色变体 (混合黑色)
|
||||
@each $i in (1, 2) {
|
||||
--wm-color-#{$type}-dark-#{$i}: #{color.mix(#000, $color, $i * 10%)};
|
||||
}
|
||||
}
|
||||
|
||||
// 文字
|
||||
--wm-color-text-primary: #{$wm-color-text-primary};
|
||||
--wm-color-text-regular: #{$wm-color-text-regular};
|
||||
--wm-color-text-secondary: #{$wm-color-text-secondary};
|
||||
--wm-color-text-placeholder: #{$wm-color-text-placeholder};
|
||||
|
||||
// 边框
|
||||
--wm-border-color-base: #{$wm-border-color-base};
|
||||
--wm-border-color-light: #{$wm-border-color-light};
|
||||
|
||||
// 背景
|
||||
--wm-bg-color-base: #{$wm-bg-color-base};
|
||||
--wm-bg-color-page: #{$wm-bg-color-page};
|
||||
}
|
||||
|
||||
// 深色主题覆盖
|
||||
[data-theme="dark"] {
|
||||
--wm-color-text-primary: rgba(255, 255, 255, 0.95);
|
||||
--wm-color-text-regular: rgba(255, 255, 255, 0.75);
|
||||
--wm-color-text-secondary: rgba(255, 255, 255, 0.55);
|
||||
--wm-color-text-placeholder: rgba(255, 255, 255, 0.35);
|
||||
|
||||
--wm-bg-color-page: #121212;
|
||||
--wm-bg-color-base: #1a1a1a;
|
||||
--wm-border-color-base: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
37
src/assets/scss/settings/_size.scss
Normal file
37
src/assets/scss/settings/_size.scss
Normal file
@ -0,0 +1,37 @@
|
||||
// 间距系统 (以8px为基准)
|
||||
$wm-spacing-base: 8px;
|
||||
|
||||
:root {
|
||||
--wm-spacing-xs: #{$wm-spacing-base * 0.5}; // 4px
|
||||
--wm-spacing-sm: #{$wm-spacing-base * 1}; // 8px
|
||||
--wm-spacing-md: #{$wm-spacing-base * 1.5}; // 12px
|
||||
--wm-spacing-lg: #{$wm-spacing-base * 2}; // 16px
|
||||
--wm-spacing-xl: #{$wm-spacing-base * 3}; // 24px
|
||||
--wm-spacing-2xl: #{$wm-spacing-base * 4}; // 32px
|
||||
}
|
||||
|
||||
// ===== 字体系统 =====
|
||||
:root {
|
||||
--wm-font-size-xs: 12px;
|
||||
--wm-font-size-sm: 14px;
|
||||
--wm-font-size-base: 16px;
|
||||
--wm-font-size-lg: 18px;
|
||||
--wm-font-size-xl: 20px;
|
||||
--wm-font-size-2xl: 24px;
|
||||
--wm-font-size-3xl: 30px;
|
||||
|
||||
--wm-font-weight-normal: 400;
|
||||
--wm-font-weight-medium: 500;
|
||||
--wm-font-weight-semibold: 600;
|
||||
|
||||
--wm-line-height-tight: 1.25;
|
||||
--wm-line-height-normal: 1.5;
|
||||
}
|
||||
|
||||
// ===== 圆角 =====
|
||||
:root {
|
||||
--wm-border-radius-sm: 4px;
|
||||
--wm-border-radius-base: 6px;
|
||||
--wm-border-radius-lg: 8px;
|
||||
--wm-border-radius-full: 9999px;
|
||||
}
|
||||
2
src/assets/scss/settings/index.scss
Normal file
2
src/assets/scss/settings/index.scss
Normal file
@ -0,0 +1,2 @@
|
||||
@use "./color";
|
||||
@use "./size";
|
||||
75
src/components/icon/icon.vue
Normal file
75
src/components/icon/icon.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<!--
|
||||
@name: icon.vue
|
||||
@description:
|
||||
@author: 程序员优雅哥 youyacoder
|
||||
@time: 2026/2/11 15:45
|
||||
-->
|
||||
<template>
|
||||
<div v-if="type === 'uno'" :class="icon" />
|
||||
|
||||
<iconify-icon v-else-if="type === 'iconify'" :icon="icon" class="icon" :aria-hidden="false" />
|
||||
|
||||
<svg v-else-if="type === 'svg'" class="svg">
|
||||
<use :href="svgSymbolId" fill="currentColor" />
|
||||
</svg>
|
||||
|
||||
<i v-else-if="type === 'iconfont'" :class="iconfontClassName" />
|
||||
|
||||
<span v-else>Unsupported type: {{ type }}</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, computed } from 'vue'
|
||||
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||
|
||||
defineOptions({ name: 'Icon' })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/**
|
||||
* Icon 图标类型:
|
||||
* - uno:UnoCSS Icon,对应的 icon 属性:https://icones.js.org/
|
||||
* - iconify: Iconify 图标,对应的 icon 属性: https://icon-sets.iconify.design/
|
||||
* - svg:本地 SVG 图标
|
||||
* - iconfont:IconFont 图标
|
||||
*/
|
||||
type?: 'uno' | 'svg' | 'iconify' | 'iconfont'
|
||||
icon: string
|
||||
prefix?: string
|
||||
fontFamily?: string
|
||||
fontUrl?: string
|
||||
}>(),
|
||||
{
|
||||
type: 'iconify',
|
||||
prefix: 'icon',
|
||||
fontFamily: 'iconfont',
|
||||
},
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.type === 'iconfont' && props.fontUrl) {
|
||||
const existingLink = document.querySelector(`link[href="${props.fontUrl}"]`)
|
||||
if (!existingLink) {
|
||||
const link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.href = props.fontUrl
|
||||
document.head.appendChild(link)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const svgSymbolId = computed(() => `#${props.prefix}-${props.icon}`)
|
||||
|
||||
const iconfontClassName = computed(() => `${props.fontFamily} ${props.prefix}-${props.icon}`)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon {
|
||||
@apply inline-block align-mid text-xl;
|
||||
}
|
||||
.svg {
|
||||
@apply inline-block align-mid;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
</style>
|
||||
38
src/i18n/index.ts
Normal file
38
src/i18n/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { App } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
/**
|
||||
* 获取浏览器的语言
|
||||
*/
|
||||
const getBrowserLanguage = () => {
|
||||
const browserLang = navigator.language || (navigator as any).userLanguage
|
||||
return ['zh-CN', 'en-US'].includes(browserLang) ? browserLang : 'zh-CN'
|
||||
}
|
||||
|
||||
const currentLang = localStorage.getItem('locale') || getBrowserLanguage()
|
||||
|
||||
// 创建 i18n 实例
|
||||
const i18n = createI18n({
|
||||
legacy: false, // 使用 Composition API
|
||||
locale: currentLang,
|
||||
fallbackLocale: 'zh-CN', // fallback 语言
|
||||
messages: {},
|
||||
})
|
||||
|
||||
/**
|
||||
* 动态加载语言包
|
||||
*/
|
||||
export async function loadLanguage(lang: string) {
|
||||
return import(`./locales/${lang}.ts`).then((messages) => {
|
||||
i18n.global.setLocaleMessage(lang, messages.default)
|
||||
localStorage.setItem('locale', lang)
|
||||
return lang
|
||||
})
|
||||
}
|
||||
|
||||
loadLanguage(currentLang)
|
||||
|
||||
export const installI18n = (app: App) => {
|
||||
app.use(i18n)
|
||||
}
|
||||
|
||||
export default i18n
|
||||
9
src/i18n/locales/en-US.ts
Normal file
9
src/i18n/locales/en-US.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
common: {
|
||||
welcome: 'Welcome, {name}!',
|
||||
},
|
||||
home: {
|
||||
title: 'Welcome to Vue3 Common Template',
|
||||
description: 'This is a feature-rich, easy-to-extend Vue3 common template project',
|
||||
},
|
||||
}
|
||||
4
src/i18n/locales/modules/en-US/demo.ts
Normal file
4
src/i18n/locales/modules/en-US/demo.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default {
|
||||
title: 'Demo Page',
|
||||
info: 'This is a demo page',
|
||||
}
|
||||
4
src/i18n/locales/modules/zh-CN/demo.ts
Normal file
4
src/i18n/locales/modules/zh-CN/demo.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default {
|
||||
title: '示例页面',
|
||||
info: '这是一个示例页面',
|
||||
}
|
||||
10
src/i18n/locales/zh-CN.ts
Normal file
10
src/i18n/locales/zh-CN.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export default {
|
||||
common: {
|
||||
hello: '你好',
|
||||
welcome: '欢迎,{name}!',
|
||||
},
|
||||
home: {
|
||||
title: '欢迎使用 Vue3 通用模板',
|
||||
description: '这是一个功能完善、易于扩展的 Vue3 通用模板项目',
|
||||
},
|
||||
}
|
||||
23
src/layouts/default.vue
Normal file
23
src/layouts/default.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<!--
|
||||
@name: default.vue.vue
|
||||
@description:
|
||||
@author: 程序员优雅哥 youyacoder
|
||||
@time: 2026/2/11 16:04
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<header>
|
||||
<router-link to="/" class="item">home</router-link>
|
||||
<router-link to="/demo" class="item">demo</router-link>
|
||||
</header>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.item {
|
||||
@apply line-height-loose mr-3 text-gray-400 mb-5;
|
||||
}
|
||||
</style>
|
||||
27
src/main.ts
Normal file
27
src/main.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './App.vue'
|
||||
import { installRouter } from './router'
|
||||
import { installStore } from '@/stores'
|
||||
import { installAssets } from '@/plugins/assets'
|
||||
import { Env } from '@/utils/env.ts'
|
||||
import { installI18n } from '@/i18n'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
installRouter(app)
|
||||
installStore(app)
|
||||
installI18n(app)
|
||||
installAssets()
|
||||
|
||||
// 获取当前模式
|
||||
const currentMode = import.meta.env.MODE
|
||||
console.log('当前模式:', currentMode) // 输出: development, uat 或 production
|
||||
console.log('VITE_APP_NAME: ', Env.get('VITE_APP_NAME'))
|
||||
console.log('VITE_NUMBER_DEMO: ', Env.getNumber('VITE_NUMBER_DEMO'))
|
||||
console.log('VITE_BOOLEAN_DEMO: ', Env.getBoolean('VITE_BOOLEAN_DEMO'))
|
||||
console.log('isDev', Env.isDev)
|
||||
console.log('isUat', Env.isUat)
|
||||
console.log('isProd', Env.isProd)
|
||||
|
||||
app.mount('#app')
|
||||
95
src/pages/demo.vue
Normal file
95
src/pages/demo.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<!--
|
||||
@name: demo.vue
|
||||
@description: Demo测试页面
|
||||
@author: 程序员优雅哥 youyacoder
|
||||
@time: 2026/2/11 13:59
|
||||
-->
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div>
|
||||
<div class="text-2xl text-danger">{{ $t('demo.title') }}</div>
|
||||
<div>{{ $t('demo.info') }}</div>
|
||||
</div>
|
||||
<h2>Pinia 测试</h2>
|
||||
<h3>计数器: {{ counter }}</h3>
|
||||
<button @click="demoStore.increment()">点击+1</button>
|
||||
|
||||
<div>
|
||||
<div class="demo-text">Hello world</div>
|
||||
<div class="text-xs text-primary">测试文字</div>
|
||||
<div class="text-sm text-success">测试文字</div>
|
||||
<div class="text-md text-info">测试文字</div>
|
||||
<div class="text-base text-warning">测试文字</div>
|
||||
<div class="text-lg text-danger">测试文字</div>
|
||||
<div class="text-xl text-text-primary">测试文字</div>
|
||||
<div class="text-2xl text-text-regular">测试文字</div>
|
||||
<div class="text-3xl text-text-secondary">测试文字</div>
|
||||
|
||||
<button class="mt-xl px-md py-sm border rounded-base" @click="toggleTheme">
|
||||
切换深色/浅色模式
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>UnoCSS Icon</div>
|
||||
<!-- A basic anchor icon from Phosphor icons -->
|
||||
<div class="i-ph-anchor-simple-thin" />
|
||||
<!-- An orange alarm from Material Design Icons -->
|
||||
<i class="i-mdi-alarm text-orange-400" />
|
||||
<!-- A large Vue logo -->
|
||||
<div class="i-logos-vue text-3xl" />
|
||||
<!-- Sun in light mode, Moon in dark mode, from Carbon -->
|
||||
<button class="i-carbon-sun dark:i-carbon-moon" />
|
||||
<!-- Twemoji of laugh, turns to tear on hovering -->
|
||||
<div
|
||||
class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div>SVG 图标</div>
|
||||
<svg aria-hidden="true" class="svg">
|
||||
<use href="#icon-demo" fill="red" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>图标组件测试</div>
|
||||
<icon type="iconify" icon="mdi:user" class="size-8 text-primary" />
|
||||
<icon type="uno" icon="i-mdi:user" class="size-8 text-primary" />
|
||||
<icon type="svg" icon="demo" class="text-primary size-8!" />
|
||||
<icon
|
||||
type="iconfont"
|
||||
font-url="//at.alicdn.com/t/c/font_3457715_h47rhjpli3n.css"
|
||||
icon="mobile-alt"
|
||||
class="text-primary text-2xl!"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDemoStore } from '@/stores/modules/demo'
|
||||
|
||||
// 初始化 Store 实例
|
||||
const demoStore = useDemoStore()
|
||||
|
||||
// 使用 storeToRefs 解构状态,保持响应性
|
||||
const { counter } = storeToRefs(demoStore)
|
||||
|
||||
const toggleTheme = () => {
|
||||
const html = document.documentElement
|
||||
const currentTheme = html.getAttribute('data-theme')
|
||||
html.setAttribute('data-theme', currentTheme === 'dark' ? 'light' : 'dark')
|
||||
}
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
locales: ['demo'],
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.demo-text {
|
||||
@apply text-xl text-primary bg-success-light-7;
|
||||
}
|
||||
</style>
|
||||
61
src/pages/index.vue
Normal file
61
src/pages/index.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<!--
|
||||
@name: index.vue
|
||||
@description: 首页
|
||||
@author: 程序员优雅哥 youyacoder
|
||||
@time: 2026/2/11 15:58
|
||||
-->
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="lang in languages"
|
||||
:key="lang.value"
|
||||
:class="{ active: currentLocale === lang.value }"
|
||||
@click="onChangeLanguage(lang.value)"
|
||||
>
|
||||
{{ lang.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div>{{ $t('common.welcome', { name: '优雅哥' }) }}</div>
|
||||
<div>{{ $t('home.title') }}</div>
|
||||
<hr />
|
||||
|
||||
<h1 class="index">index page</h1>
|
||||
<div>{{ appName }}</div>
|
||||
<div>{{ msg }}</div>
|
||||
<div>isDark: {{ isDark }}</div>
|
||||
<button @click="toggleDark()">Toggle</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { loadLanguage } from '@/i18n'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
console.log(t('common.welcome', { name: '优雅哥' }))
|
||||
const currentLocale = locale
|
||||
// 支持的语言
|
||||
const languages = [
|
||||
{ label: '中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' },
|
||||
]
|
||||
const onChangeLanguage = async (lang: string) => {
|
||||
// 加载语言包
|
||||
await loadLanguage(lang)
|
||||
// 设置当前语言
|
||||
locale.value = lang
|
||||
}
|
||||
|
||||
const msg = ref('Hello world')
|
||||
|
||||
const isDark = useDark({
|
||||
selector: 'html',
|
||||
attribute: 'data-theme',
|
||||
valueDark: 'dark',
|
||||
valueLight: 'light',
|
||||
})
|
||||
|
||||
const toggleDark = useToggle(isDark)
|
||||
|
||||
const appName = import.meta.env?.VITE_APP_NAME ?? ''
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
8
src/plugins/assets.ts
Normal file
8
src/plugins/assets.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import '@/assets/scss/index.scss'
|
||||
import 'virtual:uno.css' // 引入 UnoCSS
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
/**
|
||||
* 空方法,通过调用该方法,导入上面 import 中的样式和资源
|
||||
*/
|
||||
export const installAssets = () => {}
|
||||
56
src/router/index.ts
Normal file
56
src/router/index.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
createRouter,
|
||||
createWebHistory,
|
||||
type RouteLocationNormalized,
|
||||
type RouteRecordRaw,
|
||||
} from 'vue-router'
|
||||
import type { App } from 'vue'
|
||||
import { routes } from 'vue-router/auto-routes'
|
||||
import { setupLayouts } from 'virtual:generated-layouts'
|
||||
import i18n, { loadLanguage } from '@/i18n'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
// routes
|
||||
routes: setupLayouts(routes as RouteRecordRaw[]),
|
||||
})
|
||||
|
||||
async function loadRouteLocales(to: RouteLocationNormalized) {
|
||||
const currentLang = localStorage.getItem('locale') || 'zh-CN'
|
||||
|
||||
// 确保通用语言已加载
|
||||
await loadLanguage(currentLang)
|
||||
|
||||
// 加载路由所需的语言模块
|
||||
if (to.meta.locales) {
|
||||
for (const locale of to.meta.locales as string[]) {
|
||||
try {
|
||||
await import(`@/i18n/locales/modules/${currentLang}/${locale}.ts`).then((messages) => {
|
||||
// 合并到当前语言的消息中
|
||||
i18n.global.mergeLocaleMessage(currentLang, {
|
||||
[locale]: messages.default,
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn(`Failed to load locale module: ${locale}`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 路由前置守卫
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
await loadRouteLocales(to)
|
||||
next()
|
||||
})
|
||||
|
||||
/**
|
||||
* 安装路由
|
||||
* @param app Vue 应用实例
|
||||
* 采用模块化方法注册路由,提高代码可维护性
|
||||
*/
|
||||
export const installRouter = (app: App) => {
|
||||
app.use(router)
|
||||
}
|
||||
|
||||
export default router
|
||||
21
src/stores/index.ts
Normal file
21
src/stores/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import type { App } from 'vue'
|
||||
|
||||
// 创建 Pinia 实例
|
||||
const pinia = createPinia()
|
||||
|
||||
// 添加持久化插件
|
||||
// 该插件会自动将状态保存到 localStorage,默认 key 为 store 名称
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
/**
|
||||
* 安装 Pinia
|
||||
* @param app Vue 应用实例
|
||||
* 采用模块化方法注册 Pinia,与路由注册保持一致的风格
|
||||
*/
|
||||
export const installStore = (app: App) => {
|
||||
app.use(pinia)
|
||||
}
|
||||
|
||||
export default pinia
|
||||
28
src/stores/modules/demo.ts
Normal file
28
src/stores/modules/demo.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
/**
|
||||
* 测试用的 Pinia Store
|
||||
* 使用组合式 API 风格编写,与 Vue3 的组合式 API 保持一致
|
||||
*/
|
||||
export const useDemoStore = defineStore(
|
||||
'demoStore', // Store 唯一标识
|
||||
() => {
|
||||
// 状态定义(类似 Vue 的 ref)
|
||||
const counter = ref<number>(0)
|
||||
|
||||
// Action 定义(修改状态的方法)
|
||||
const increment = () => {
|
||||
counter.value++
|
||||
}
|
||||
|
||||
// 返回需要暴露的状态和方法
|
||||
return {
|
||||
counter,
|
||||
increment,
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: true, // 开启状态持久化
|
||||
},
|
||||
)
|
||||
53
src/utils/env.ts
Normal file
53
src/utils/env.ts
Normal file
@ -0,0 +1,53 @@
|
||||
export class Env {
|
||||
/**
|
||||
* 获取环境变量
|
||||
* @param key 环境变量名
|
||||
* @param defaultValue 默认值
|
||||
*/
|
||||
static get<T>(key: keyof ImportMetaEnv, defaultValue?: T): T | string {
|
||||
const value = import.meta.env[key]
|
||||
return value ?? (defaultValue as T)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数字类型的环境变量
|
||||
*/
|
||||
static getNumber(key: keyof ImportMetaEnv, defaultValue?: number): number {
|
||||
const value = import.meta.env[key]
|
||||
if (value === undefined) {
|
||||
return defaultValue as number
|
||||
}
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔类型的环境变量
|
||||
*/
|
||||
static getBoolean(key: keyof ImportMetaEnv, defaultValue?: boolean): boolean {
|
||||
const value = import.meta.env[key]
|
||||
if (value === undefined) {
|
||||
return defaultValue as boolean
|
||||
}
|
||||
return value === 'true' || value === '1'
|
||||
}
|
||||
|
||||
/** 获取当前环境 */
|
||||
static get env(): 'dev' | 'uat' | 'prod' {
|
||||
return this.get('VITE_ENV', 'dev') as 'dev' | 'uat' | 'prod'
|
||||
}
|
||||
|
||||
/** 是否为开发环境 */
|
||||
static get isDev(): boolean {
|
||||
return this.env === 'dev'
|
||||
}
|
||||
|
||||
/** 是否为UAT环境 */
|
||||
static get isUat(): boolean {
|
||||
return this.env === 'uat'
|
||||
}
|
||||
|
||||
/** 是否为生产环境 */
|
||||
static get isProd(): boolean {
|
||||
return this.env === 'prod'
|
||||
}
|
||||
}
|
||||
4950
stats.html
Normal file
4950
stats.html
Normal file
File diff suppressed because one or more lines are too long
19
tsconfig.app.json
Normal file
19
tsconfig.app.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": [
|
||||
"env.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.vue",
|
||||
"typed-router.d.ts",
|
||||
"auto-imports.d.ts",
|
||||
"components.d.ts"
|
||||
],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node24/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
77
typed-router.d.ts
vendored
Normal file
77
typed-router.d.ts
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection ES6UnusedImports
|
||||
// Generated by unplugin-vue-router. !! DO NOT MODIFY THIS FILE !!
|
||||
// It's recommended to commit this file.
|
||||
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||
|
||||
declare module 'vue-router/auto-resolver' {
|
||||
export type ParamParserCustom = never
|
||||
}
|
||||
|
||||
declare module 'vue-router/auto-routes' {
|
||||
import type {
|
||||
RouteRecordInfo,
|
||||
ParamValue,
|
||||
ParamValueOneOrMore,
|
||||
ParamValueZeroOrMore,
|
||||
ParamValueZeroOrOne,
|
||||
} from 'vue-router'
|
||||
|
||||
/**
|
||||
* Route name map generated by unplugin-vue-router
|
||||
*/
|
||||
export interface RouteNamedMap {
|
||||
'/': RouteRecordInfo<
|
||||
'/',
|
||||
'/',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/demo': RouteRecordInfo<
|
||||
'/demo',
|
||||
'/demo',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
}
|
||||
|
||||
/**
|
||||
* Route file to route info map by unplugin-vue-router.
|
||||
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
|
||||
*
|
||||
* Each key is a file path relative to the project root with 2 properties:
|
||||
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
|
||||
* - views: names of nested views (can be passed to <RouterView name="...">)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface _RouteFileInfoMap {
|
||||
'src/pages/index.vue': {
|
||||
routes:
|
||||
| '/'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'src/pages/demo.vue': {
|
||||
routes:
|
||||
| '/demo'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a union of possible route names in a certain route component file.
|
||||
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type _RouteNamesForFilePath<FilePath extends string> =
|
||||
_RouteFileInfoMap extends Record<FilePath, infer Info>
|
||||
? Info['routes']
|
||||
: keyof RouteNamedMap
|
||||
}
|
||||
86
uno.config.ts
Normal file
86
uno.config.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import {
|
||||
defineConfig,
|
||||
presetWind4,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
presetIcons,
|
||||
} from 'unocss'
|
||||
|
||||
export default defineConfig({
|
||||
presets: [
|
||||
presetWind4({
|
||||
preflights: { reset: true },
|
||||
}),
|
||||
presetIcons({
|
||||
prefix: 'i-',
|
||||
extraProperties: {
|
||||
display: 'inline-block',
|
||||
'vertical-align': 'middle',
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
transformers: [
|
||||
// 启用 @apply 指令支持
|
||||
transformerDirectives(),
|
||||
// 启用 `hover:(bg-gray-400 text-white)` 类写法
|
||||
transformerVariantGroup(),
|
||||
],
|
||||
|
||||
theme: {
|
||||
colors: {
|
||||
// 品牌与状态色
|
||||
primary: 'var(--wm-color-primary)',
|
||||
success: 'var(--wm-color-success)',
|
||||
warning: 'var(--wm-color-warning)',
|
||||
danger: 'var(--wm-color-danger)',
|
||||
info: 'var(--wm-color-info)',
|
||||
// 文字色
|
||||
'text-primary': 'var(--wm-color-text-primary)',
|
||||
'text-regular': 'var(--wm-color-text-regular)',
|
||||
'text-secondary': 'var(--wm-color-text-secondary)',
|
||||
// 背景色
|
||||
'bg-page': 'var(--wm-bg-color-page)',
|
||||
'bg-base': 'var(--wm-bg-color-base)',
|
||||
},
|
||||
spacing: {
|
||||
xs: 'var(--wm-spacing-xs)',
|
||||
sm: 'var(--wm-spacing-sm)',
|
||||
md: 'var(--wm-spacing-md)',
|
||||
lg: 'var(--wm-spacing-lg)',
|
||||
xl: 'var(--wm-spacing-xl)',
|
||||
'2xl': 'var(--wm-spacing-2xl)',
|
||||
},
|
||||
fontSize: {
|
||||
xs: 'var(--wm-font-size-xs)',
|
||||
sm: 'var(--wm-font-size-sm)',
|
||||
base: 'var(--wm-font-size-base)',
|
||||
lg: 'var(--wm-font-size-lg)',
|
||||
xl: 'var(--wm-font-size-xl)',
|
||||
'2xl': 'var(--wm-font-size-2xl)',
|
||||
'3xl': 'var(--wm-font-size-3xl)',
|
||||
},
|
||||
borderRadius: {
|
||||
sm: 'var(--wm-border-radius-sm)',
|
||||
base: 'var(--wm-border-radius-base)',
|
||||
lg: 'var(--wm-border-radius-lg)',
|
||||
full: 'var(--wm-border-radius-full)',
|
||||
},
|
||||
},
|
||||
|
||||
rules: [
|
||||
...['primary', 'success', 'warning', 'danger', 'info'].flatMap((color) => [
|
||||
[`bg-${color}-light-3`, { 'background-color': `var(--wm-color-${color}-light-3)` }],
|
||||
[`bg-${color}-light-5`, { 'background-color': `var(--wm-color-${color}-light-5)` }],
|
||||
[`bg-${color}-light-7`, { 'background-color': `var(--wm-color-${color}-light-7)` }],
|
||||
[`bg-${color}-light-9`, { 'background-color': `var(--wm-color-${color}-light-9)` }],
|
||||
[`text-${color}-dark-2`, { color: `var(--wm-color-${color}-dark-2)` }],
|
||||
]),
|
||||
],
|
||||
|
||||
shortcuts: {
|
||||
// 布局
|
||||
'flex-center': 'flex justify-center items-center',
|
||||
'flex-col-center': 'flex flex-col justify-center items-center',
|
||||
},
|
||||
})
|
||||
151
vite.config.ts
Normal file
151
vite.config.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import VueRouter from 'unplugin-vue-router/vite'
|
||||
import Layouts from 'vite-plugin-vue-layouts'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import { VueRouterAutoImports } from 'unplugin-vue-router'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import vueI18n from '@intlify/unplugin-vue-i18n/vite'
|
||||
import path from 'node:path'
|
||||
import { compression } from 'vite-plugin-compression2'
|
||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
|
||||
import legacy from '@vitejs/plugin-legacy'
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), 'VITE_')
|
||||
// 如果你有其他场景判断,可修改此处,本文假设只针对生产环境
|
||||
const isProd = mode === 'prod'
|
||||
|
||||
return {
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
assetsInlineLimit: 4096,
|
||||
reportCompressedSize: true,
|
||||
minify: 'terser',
|
||||
cssCodeSplit: true,
|
||||
sourcemap: !isProd,
|
||||
terserOptions: isProd
|
||||
? {
|
||||
// 代码压缩配置
|
||||
compress: {
|
||||
drop_console: true, // 移除 console
|
||||
drop_debugger: true, // 移除 debugger
|
||||
},
|
||||
// 代码混淆配置
|
||||
mangle: {
|
||||
toplevel: true, // 混淆顶层变量名
|
||||
eval: true, // 混淆 eval 中的变量
|
||||
},
|
||||
// 输出配置
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
}
|
||||
: {},
|
||||
olldownOptions: {
|
||||
output: {
|
||||
codeSplitting: {
|
||||
groups: [
|
||||
{
|
||||
name: 'vue',
|
||||
test: /node_modules[\\/]vue/,
|
||||
priority: 20,
|
||||
},
|
||||
{
|
||||
name: 'vue-router',
|
||||
test: /node_modules[\\/]vue-router/,
|
||||
priority: 15,
|
||||
},
|
||||
{
|
||||
name: 'pinia',
|
||||
test: /node_modules[\\/]pinia/,
|
||||
priority: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
VueRouter({}),
|
||||
// ⚠️ Vue must be placed after VueRouter()
|
||||
vue(),
|
||||
vueJsx(),
|
||||
UnoCSS(), // 配置 SVG 图标插件
|
||||
createSvgIconsPlugin({
|
||||
// SVG 图标目录
|
||||
iconDirs: [fileURLToPath(new URL('./src/assets/icons', import.meta.url))],
|
||||
// 生成的 symbol ID 格式
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
}),
|
||||
Layouts({
|
||||
layoutsDirs: 'src/layouts',
|
||||
defaultLayout: 'default',
|
||||
}),
|
||||
AutoImport({
|
||||
include: [
|
||||
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
|
||||
/\.vue$/,
|
||||
/\.vue\?vue/, // .vue
|
||||
/\.md$/, // .md
|
||||
],
|
||||
imports: ['vue', VueRouterAutoImports, 'pinia', '@vueuse/core'],
|
||||
}),
|
||||
Components({
|
||||
deep: true,
|
||||
directoryAsNamespace: false,
|
||||
}),
|
||||
vueI18n({
|
||||
// 语言包目录
|
||||
include: path.resolve(__dirname, './src/i18n/locales/**'),
|
||||
// 开发模式下也启动编译时处理
|
||||
runtimeOnly: false,
|
||||
// 仅使用组合式 API
|
||||
compositionOnly: true,
|
||||
// 完整安装
|
||||
fullInstall: true,
|
||||
}),
|
||||
// Gzip 压缩
|
||||
compression({
|
||||
algorithms: ['gzip'],
|
||||
threshold: 10240, // 超过 10KB 的文件才压缩
|
||||
deleteOriginalAssets: false, // 不删除原文件
|
||||
}),
|
||||
ViteImageOptimizer({
|
||||
png: {
|
||||
quality: 80,
|
||||
},
|
||||
jpeg: {
|
||||
quality: 80,
|
||||
},
|
||||
webp: {
|
||||
quality: 80,
|
||||
},
|
||||
}),
|
||||
legacy({
|
||||
targets: ['defaults', 'not IE 11'],
|
||||
}),
|
||||
visualizer({
|
||||
filename: 'stats.html',
|
||||
}),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
|
||||
},
|
||||
}
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user