diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dd76572 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# http://editorconfig.org +root = true + +# 表示所有文件适用 +[*] +charset = utf-8 # 设置文件字符集为 utf-8 +end_of_line = lf # 控制换行类型(lf | cr | crlf) +indent_style = tab # 缩进风格(tab | space) +insert_final_newline = true # 始终在文件末尾插入一个新行 + +# 表示仅 md 文件适用以下规则 +[*.md] +max_line_length = off # 关闭最大行长度限制 +trim_trailing_whitespace = false # 关闭末尾空格修剪 diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..c317de3 --- /dev/null +++ b/.env.development @@ -0,0 +1,9 @@ +## 开发环境 + +# 变量必须以 VITE_ 为前缀才能暴露给外部读取 +NODE_ENV='development' + +VITE_APP_TITLE = 'vue3-element-admin' +VITE_APP_PORT = 9999 +VITE_APP_BASE_API = '/dev-api' +VITE_APP_BASE_URL = 'http://myvuetest.net' diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..e7e4514 --- /dev/null +++ b/.env.production @@ -0,0 +1,5 @@ +## 生产环境 + +VITE_APP_TITLE = '恒信高科' +VITE_APP_PORT = 17777 +VITE_APP_BASE_API = '/systemapi' diff --git a/.env.staging b/.env.staging new file mode 100644 index 0000000..2d124a6 --- /dev/null +++ b/.env.staging @@ -0,0 +1,6 @@ +## 模拟环境 +NODE_ENV='staging' + +VITE_APP_TITLE = '恒信高科' +VITE_APP_PORT = 3000 +VITE_APP_BASE_API = '/prod--api' diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..43af40f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,14 @@ +dist +node_modules +public +.husky +.vscode +.idea +*.sh +*.md + +src/assets + +.eslintrc.cjs +.prettierrc.cjs +.stylelintrc.cjs diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json new file mode 100644 index 0000000..9f36942 --- /dev/null +++ b/.eslintrc-auto-import.json @@ -0,0 +1,269 @@ +{ + "globals": { + "EffectScope": true, + "ElForm": true, + "ElMessage": true, + "ElMessageBox": true, + "ElTree": 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, + "createReactiveFn": true, + "createSharedComposable": true, + "createUnrefFn": true, + "customRef": true, + "debouncedRef": true, + "debouncedWatch": true, + "defineAsyncComponent": true, + "defineComponent": true, + "eagerComputed": true, + "effectScope": true, + "extendRef": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "ignorableWatch": true, + "inject": true, + "isDefined": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "makeDestructurable": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": 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, + "pausableWatch": true, + "provide": 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, + "resolveDirective": true, + "resolveRef": true, + "resolveUnref": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "syncRef": true, + "syncRefs": true, + "templateRef": true, + "throttledRef": true, + "throttledWatch": true, + "toRaw": true, + "toReactive": true, + "toRef": true, + "toRefs": true, + "triggerRef": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unref": true, + "unrefElement": true, + "until": true, + "useActiveElement": true, + "useArrayEvery": true, + "useArrayFilter": true, + "useArrayFind": true, + "useArrayFindIndex": true, + "useArrayFindLast": 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, + "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, + "useIdle": true, + "useImage": true, + "useInfiniteScroll": true, + "useIntersectionObserver": true, + "useInterval": true, + "useIntervalFn": true, + "useKeyModifier": true, + "useLastChanged": true, + "useLocalStorage": true, + "useMagicKeys": true, + "useManualRefHistory": true, + "useMediaControls": true, + "useMediaQuery": true, + "useMemoize": true, + "useMemory": 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, + "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, + "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, + "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, + "watchEffect": true, + "watchIgnorable": true, + "watchOnce": true, + "watchPausable": true, + "watchPostEffect": true, + "watchSyncEffect": true, + "watchThrottled": true, + "watchTriggerable": true, + "watchWithFilter": true, + "whenever": true + } +} \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..e4abf99 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,32 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + parser: "vue-eslint-parser", + extends: [ + // 参考vuejs官方的eslint配置: https://eslint.vuejs.org/user-guide/#usage + "plugin:vue/vue3-recommended", + "./.eslintrc-auto-import.json", + "prettier", + ], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: "@typescript-eslint/parser", + }, + plugins: ["vue", "@typescript-eslint"], + rules: { + "vue/multi-word-component-names": "off", // 关闭组件名必须多字: https://eslint.vuejs.org/rules/multi-word-component-names.html + "@typescript-eslint/no-empty-function": "off", // 关闭空方法检查 + "@typescript-eslint/no-explicit-any": "off", // 关闭any类型的警告 + "vue/no-v-model-argument": "off", + "@typescript-eslint/no-non-null-assertion": "off", + }, + // https://eslint.org/docs/latest/use/configure/language-options#specifying-globals + globals: { + DialogOption: "readonly", + OptionType: "readonly", + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5c3e83 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +pnpm-lock.yaml diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..e8511ea --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no-install commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..37568d1 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run lint:lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d751f74 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +dist +node_modules +public +.husky +.vscode +.idea +*.sh +*.md + +src/assets diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..d39f28c --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,36 @@ +module.exports = { + // (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always) + arrowParens: "always", + // 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false + bracketSameLine: false, + // 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar}) + bracketSpacing: true, + // 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto) + embeddedLanguageFormatting: "auto", + // 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css) + htmlWhitespaceSensitivity: "css", + // 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false + insertPragma: false, + // 在 JSX 中使用单引号替代双引号,默认false + jsxSingleQuote: false, + // 每行最多字符数量,超出换行(默认80) + printWidth: 80, + // 超出打印宽度 (always | never | preserve ) + proseWrap: "preserve", + // 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;) + quoteProps: "as-needed", + // 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false + requirePragma: false, + // 结尾添加分号 + semi: true, + // 使用单引号 (true:单引号;false:双引号) + singleQuote: false, + // 缩进空格数,默认2个空格 + tabWidth: 2, + // 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号 + trailingComma: "es5", + // 指定缩进方式,空格或tab,默认false,即使用空格 + useTabs: false, + // vue 文件中是否缩进 \ No newline at end of file diff --git a/src/assets/icons/chart.svg b/src/assets/icons/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/src/assets/icons/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/client.svg b/src/assets/icons/client.svg new file mode 100644 index 0000000..ad4bc15 --- /dev/null +++ b/src/assets/icons/client.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close.svg b/src/assets/icons/close.svg new file mode 100644 index 0000000..5b5057f --- /dev/null +++ b/src/assets/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close_all.svg b/src/assets/icons/close_all.svg new file mode 100644 index 0000000..aa13cd7 --- /dev/null +++ b/src/assets/icons/close_all.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close_left.svg b/src/assets/icons/close_left.svg new file mode 100644 index 0000000..e5708ea --- /dev/null +++ b/src/assets/icons/close_left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close_other.svg b/src/assets/icons/close_other.svg new file mode 100644 index 0000000..212e6c2 --- /dev/null +++ b/src/assets/icons/close_other.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/close_right.svg b/src/assets/icons/close_right.svg new file mode 100644 index 0000000..14d3cf3 --- /dev/null +++ b/src/assets/icons/close_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/coupon.svg b/src/assets/icons/coupon.svg new file mode 100644 index 0000000..2f952b2 --- /dev/null +++ b/src/assets/icons/coupon.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/dashboard.svg b/src/assets/icons/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/src/assets/icons/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/dict.svg b/src/assets/icons/dict.svg new file mode 100644 index 0000000..22a8278 --- /dev/null +++ b/src/assets/icons/dict.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/assets/icons/dict_item.svg b/src/assets/icons/dict_item.svg new file mode 100644 index 0000000..903109a --- /dev/null +++ b/src/assets/icons/dict_item.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/assets/icons/document.svg b/src/assets/icons/document.svg new file mode 100644 index 0000000..918ae33 --- /dev/null +++ b/src/assets/icons/document.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/download.svg b/src/assets/icons/download.svg new file mode 100644 index 0000000..61ec1f9 --- /dev/null +++ b/src/assets/icons/download.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/drag.svg b/src/assets/icons/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/src/assets/icons/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/edit.svg b/src/assets/icons/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/src/assets/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/exit-fullscreen.svg b/src/assets/icons/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/src/assets/icons/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/eye-open.svg b/src/assets/icons/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/src/assets/icons/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/eye.svg b/src/assets/icons/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/src/assets/icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/fullscreen.svg b/src/assets/icons/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/src/assets/icons/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/github.svg b/src/assets/icons/github.svg new file mode 100644 index 0000000..db0a0d4 --- /dev/null +++ b/src/assets/icons/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/goods-list.svg b/src/assets/icons/goods-list.svg new file mode 100644 index 0000000..fcb971e --- /dev/null +++ b/src/assets/icons/goods-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/goods.svg b/src/assets/icons/goods.svg new file mode 100644 index 0000000..60c1c73 --- /dev/null +++ b/src/assets/icons/goods.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/guide.svg b/src/assets/icons/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/src/assets/icons/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/homepage.svg b/src/assets/icons/homepage.svg new file mode 100644 index 0000000..48f4e24 --- /dev/null +++ b/src/assets/icons/homepage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/lab.svg b/src/assets/icons/lab.svg new file mode 100644 index 0000000..d4d60aa --- /dev/null +++ b/src/assets/icons/lab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/language.svg b/src/assets/icons/language.svg new file mode 100644 index 0000000..d2dd693 --- /dev/null +++ b/src/assets/icons/language.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/link.svg b/src/assets/icons/link.svg new file mode 100644 index 0000000..9748d53 --- /dev/null +++ b/src/assets/icons/link.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/menu.svg b/src/assets/icons/menu.svg new file mode 100644 index 0000000..92c364c --- /dev/null +++ b/src/assets/icons/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/message.svg b/src/assets/icons/message.svg new file mode 100644 index 0000000..ea1ddef --- /dev/null +++ b/src/assets/icons/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/money.svg b/src/assets/icons/money.svg new file mode 100644 index 0000000..60f7acf --- /dev/null +++ b/src/assets/icons/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/monitor.svg b/src/assets/icons/monitor.svg new file mode 100644 index 0000000..bc308cb --- /dev/null +++ b/src/assets/icons/monitor.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/multi_level.svg b/src/assets/icons/multi_level.svg new file mode 100644 index 0000000..a1a2792 --- /dev/null +++ b/src/assets/icons/multi_level.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/nested.svg b/src/assets/icons/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/src/assets/icons/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/number.svg b/src/assets/icons/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/src/assets/icons/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/order.svg b/src/assets/icons/order.svg new file mode 100644 index 0000000..8f2107e --- /dev/null +++ b/src/assets/icons/order.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/password.svg b/src/assets/icons/password.svg new file mode 100644 index 0000000..6c64def --- /dev/null +++ b/src/assets/icons/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/peoples.svg b/src/assets/icons/peoples.svg new file mode 100644 index 0000000..383b82d --- /dev/null +++ b/src/assets/icons/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/perm.svg b/src/assets/icons/perm.svg new file mode 100644 index 0000000..b38d065 --- /dev/null +++ b/src/assets/icons/perm.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/publish.svg b/src/assets/icons/publish.svg new file mode 100644 index 0000000..e9b489c --- /dev/null +++ b/src/assets/icons/publish.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/rabbitmq.svg b/src/assets/icons/rabbitmq.svg new file mode 100644 index 0000000..65aa198 --- /dev/null +++ b/src/assets/icons/rabbitmq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/rate.svg b/src/assets/icons/rate.svg new file mode 100644 index 0000000..aa3b14d --- /dev/null +++ b/src/assets/icons/rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/redis.svg b/src/assets/icons/redis.svg new file mode 100644 index 0000000..2f1d62d --- /dev/null +++ b/src/assets/icons/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/refresh.svg b/src/assets/icons/refresh.svg new file mode 100644 index 0000000..1f549f1 --- /dev/null +++ b/src/assets/icons/refresh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/role.svg b/src/assets/icons/role.svg new file mode 100644 index 0000000..c484b13 --- /dev/null +++ b/src/assets/icons/role.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/security.svg b/src/assets/icons/security.svg new file mode 100644 index 0000000..bcd9d2e --- /dev/null +++ b/src/assets/icons/security.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/shopping.svg b/src/assets/icons/shopping.svg new file mode 100644 index 0000000..8d2b4bf --- /dev/null +++ b/src/assets/icons/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/size.svg b/src/assets/icons/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/src/assets/icons/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/skill.svg b/src/assets/icons/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/src/assets/icons/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/system.svg b/src/assets/icons/system.svg new file mode 100644 index 0000000..e3b7e2d --- /dev/null +++ b/src/assets/icons/system.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/theme.svg b/src/assets/icons/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/src/assets/icons/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/tree.svg b/src/assets/icons/tree.svg new file mode 100644 index 0000000..d40a414 --- /dev/null +++ b/src/assets/icons/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/user.svg b/src/assets/icons/user.svg new file mode 100644 index 0000000..e4c7b38 --- /dev/null +++ b/src/assets/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/uv.svg b/src/assets/icons/uv.svg new file mode 100644 index 0000000..ca4c301 --- /dev/null +++ b/src/assets/icons/uv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/valid_code.svg b/src/assets/icons/valid_code.svg new file mode 100644 index 0000000..39bf478 --- /dev/null +++ b/src/assets/icons/valid_code.svg @@ -0,0 +1,9 @@ + + + + diff --git a/src/assets/icons/verify_code.svg b/src/assets/icons/verify_code.svg new file mode 100644 index 0000000..39bf478 --- /dev/null +++ b/src/assets/icons/verify_code.svg @@ -0,0 +1,9 @@ + + + + diff --git a/src/assets/index/indicator.png b/src/assets/index/indicator.png new file mode 100644 index 0000000..2f53da6 Binary files /dev/null and b/src/assets/index/indicator.png differ diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..a17fc3f --- /dev/null +++ b/src/components/Breadcrumb/index.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/src/components/GithubCorner/index.vue b/src/components/GithubCorner/index.vue new file mode 100644 index 0000000..6fadd6c --- /dev/null +++ b/src/components/GithubCorner/index.vue @@ -0,0 +1,62 @@ + + + diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue new file mode 100644 index 0000000..d98a4aa --- /dev/null +++ b/src/components/Hamburger/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue new file mode 100644 index 0000000..bea826d --- /dev/null +++ b/src/components/IconSelect/index.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/src/components/LangSelect/index.vue b/src/components/LangSelect/index.vue new file mode 100644 index 0000000..4880cd0 --- /dev/null +++ b/src/components/LangSelect/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue new file mode 100644 index 0000000..2953fab --- /dev/null +++ b/src/components/Pagination/index.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/RightPanel/index.vue b/src/components/RightPanel/index.vue new file mode 100644 index 0000000..01eb464 --- /dev/null +++ b/src/components/RightPanel/index.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..a48f914 --- /dev/null +++ b/src/components/SizeSelect/index.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..07b65e8 --- /dev/null +++ b/src/components/SvgIcon/index.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/components/Upload/MultiUpload.vue b/src/components/Upload/MultiUpload.vue new file mode 100644 index 0000000..1194509 --- /dev/null +++ b/src/components/Upload/MultiUpload.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/components/Upload/SingleUpload.vue b/src/components/Upload/SingleUpload.vue new file mode 100644 index 0000000..4038c72 --- /dev/null +++ b/src/components/Upload/SingleUpload.vue @@ -0,0 +1,81 @@ + + + + + + + diff --git a/src/components/WangEditor/index.vue b/src/components/WangEditor/index.vue new file mode 100644 index 0000000..5c7b4ef --- /dev/null +++ b/src/components/WangEditor/index.vue @@ -0,0 +1,90 @@ + + + + + + + diff --git a/src/directive/index.ts b/src/directive/index.ts new file mode 100644 index 0000000..960fa44 --- /dev/null +++ b/src/directive/index.ts @@ -0,0 +1,9 @@ +import type { App } from 'vue'; + +import { hasPerm } from './permission'; + +// 全局注册 directive +export function setupDirective(app: App) { + // 使 v-hasPerm 在所有组件中都可用 + app.directive('hasPerm', hasPerm); +} diff --git a/src/directive/permission/index.ts b/src/directive/permission/index.ts new file mode 100644 index 0000000..b8d0dee --- /dev/null +++ b/src/directive/permission/index.ts @@ -0,0 +1,55 @@ +import { useUserStoreHook } from '@/store/modules/user'; +import { Directive, DirectiveBinding } from 'vue'; + +/** + * 按钮权限 + */ +export const hasPerm: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + // 「超级管理员」拥有所有的按钮权限 + const { roles, perms } = useUserStoreHook(); + if (roles.includes('ROOT')) { + return true; + } + // 「其他角色」按钮权限校验 + const { value } = binding; + if (value) { + const requiredPerms = value; // DOM绑定需要的按钮权限标识 + + const hasPerm = perms?.some(perm => { + return requiredPerms.includes(perm); + }); + + if (!hasPerm) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error( + "need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\"" + ); + } + } +}; + +/** + * 角色权限 + */ +export const hasRole: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding; + + if (value) { + const requiredRoles = value; // DOM绑定需要的角色编码 + const { roles } = useUserStoreHook(); + const hasRole = roles.some(perm => { + return requiredRoles.includes(perm); + }); + + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error("need roles! Like v-has-role=\"['admin','test']\""); + } + } +}; diff --git a/src/enums/MenuTypeEnum.ts b/src/enums/MenuTypeEnum.ts new file mode 100644 index 0000000..96c612e --- /dev/null +++ b/src/enums/MenuTypeEnum.ts @@ -0,0 +1,19 @@ +export enum MenuTypeEnum { + /** + * 目录 + */ + CATALOG = 'CATALOG', + /** + * 菜单 + */ + MENU = 'MENU', + + /** + * 按钮 + */ + BUTTON = 'BUTTON', + /** + * 外链 + */ + EXTLINK = 'EXTLINK' +} diff --git a/src/lang/index.ts b/src/lang/index.ts new file mode 100644 index 0000000..7f3ad89 --- /dev/null +++ b/src/lang/index.ts @@ -0,0 +1,25 @@ +import { createI18n } from 'vue-i18n'; +import { useAppStore } from '@/store/modules/app'; + +const appStore = useAppStore(); +// 本地语言包 +import enLocale from './package/en'; +import zhCnLocale from './package/zh-cn'; + +const messages = { + 'zh-cn': { + ...zhCnLocale + }, + en: { + ...enLocale + } +}; + +const i18n = createI18n({ + legacy: false, + locale: appStore.language, + messages: messages, + globalInjection: true +}); + +export default i18n; diff --git a/src/lang/package/en.ts b/src/lang/package/en.ts new file mode 100644 index 0000000..e93d359 --- /dev/null +++ b/src/lang/package/en.ts @@ -0,0 +1,22 @@ +export default { + // 路由国际化 + route: { + dashboard: 'Dashboard', + document: 'Document' + }, + // 登录页面国际化 + login: { + title: 'Hengxin High Tech Application Platform', + username: 'Username', + password: 'Password', + login: 'Login', + verifyCode: 'Verify Code', + }, + // 导航栏国际化 + navbar: { + dashboard: 'Dashboard', + logout: 'Logout', + document: 'Document', + gitee: 'Gitee' + } +}; diff --git a/src/lang/package/zh-cn.ts b/src/lang/package/zh-cn.ts new file mode 100644 index 0000000..ef9440c --- /dev/null +++ b/src/lang/package/zh-cn.ts @@ -0,0 +1,22 @@ +export default { + // 路由国际化 + route: { + dashboard: '首页', + document: '项目文档' + }, + // 登录页面国际化 + login: { + title: '恒信高科应用平台', + username: '用户名', + password: '密码', + login: '登 录', + verifyCode: '验证码' + }, + // 导航栏国际化 + navbar: { + dashboard: '首页', + logout: '注销', + document: '项目文档', + gitee: '码云' + } +}; diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue new file mode 100644 index 0000000..0de8378 --- /dev/null +++ b/src/layout/components/AppMain.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue new file mode 100644 index 0000000..bc99f34 --- /dev/null +++ b/src/layout/components/Navbar.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..c1a0451 --- /dev/null +++ b/src/layout/components/Settings/index.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..8e9337c --- /dev/null +++ b/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,37 @@ + + + diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..9988a75 --- /dev/null +++ b/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..ebb6e2d --- /dev/null +++ b/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,121 @@ + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..c76bed9 --- /dev/null +++ b/src/layout/components/Sidebar/index.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..f2b7e7a --- /dev/null +++ b/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..5810470 --- /dev/null +++ b/src/layout/components/TagsView/index.vue @@ -0,0 +1,373 @@ + + + + + diff --git a/src/layout/components/index.ts b/src/layout/components/index.ts new file mode 100644 index 0000000..4dca96e --- /dev/null +++ b/src/layout/components/index.ts @@ -0,0 +1,4 @@ +export { default as Navbar } from './Navbar.vue'; +export { default as AppMain } from './AppMain.vue'; +export { default as Settings } from './Settings/index.vue'; +export { default as TagsView } from './TagsView/index.vue'; diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..dda75b3 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..1d10f2f --- /dev/null +++ b/src/main.ts @@ -0,0 +1,26 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import router from '@/router'; +import { setupStore } from '@/store'; +import { setupDirective } from '@/directive'; + +import '@/permission'; + +// 本地SVG图标 +import 'virtual:svg-icons-register'; + +// 国际化 +import i18n from '@/lang/index'; + +// 样式 +import 'element-plus/theme-chalk/dark/css-vars.css'; +import '@/styles/index.scss'; +import 'uno.css'; + +const app = createApp(App); +// 全局注册 自定义指令(directive) +setupDirective(app); +// 全局注册 状态管理(store) +setupStore(app); + +app.use(router).use(i18n).mount('#app'); diff --git a/src/permission.ts b/src/permission.ts new file mode 100644 index 0000000..6b1203e --- /dev/null +++ b/src/permission.ts @@ -0,0 +1,61 @@ +import router from "@/router"; +import { useUserStoreHook } from "@/store/modules/user"; +import { usePermissionStoreHook } from "@/store/modules/permission"; + +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; +NProgress.configure({ showSpinner: false }); // 进度条 + +const permissionStore = usePermissionStoreHook(); + +// 白名单路由 +const whiteList = ["/login"]; + +router.beforeEach(async (to, from, next) => { + NProgress.start(); + const hasToken = localStorage.getItem("accessToken"); + if (hasToken) { + if (to.path === "/login") { + // 如果已登录,跳转首页 + next({ path: "/" }); + NProgress.done(); + } else { + const userStore = useUserStoreHook(); + const hasRoles = userStore.roles && userStore.roles.length > 0; + if (hasRoles) { + // 未匹配到任何路由,跳转404 + if (to.matched.length === 0) { + from.name ? next({ name: from.name }) : next("/404"); + } else { + next(); + } + } else { + try { + const { roles } = await userStore.getInfo(); + const accessRoutes = await permissionStore.generateRoutes(roles); + accessRoutes.forEach((route) => { + router.addRoute(route); + }); + next({ ...to, replace: true }); + } catch (error) { + // 移除 token 并跳转登录页 + await userStore.resetToken(); + next(`/login?redirect=${to.path}`); + NProgress.done(); + } + } + } + } else { + // 未登录可以访问白名单页面 + if (whiteList.indexOf(to.path) !== -1) { + next(); + } else { + next(`/login?redirect=${to.path}`); + NProgress.done(); + } + } +}); + +router.afterEach(() => { + NProgress.done(); +}); diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..c880194 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,119 @@ +import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"; + +export const Layout = () => import("@/layout/index.vue"); + +// 静态路由 +export const constantRoutes: RouteRecordRaw[] = [ + { + path: "/redirect", + component: Layout, + meta: { hidden: true }, + children: [ + { + path: "/redirect/:path(.*)", + component: () => import("@/views/redirect/index.vue"), + }, + ], + }, + + { + path: "/login", + component: () => import("@/views/login/index.vue"), + meta: { hidden: true }, + }, + + { + path: "/", + component: Layout, + redirect: "/dashboard", + children: [ + { + path: "dashboard", + component: () => import("@/views/dashboard/index.vue"), + name: "Dashboard", + meta: { title: "dashboard", icon: "homepage", affix: true }, + }, + { + path: "401", + component: () => import("@/views/error-page/401.vue"), + meta: { hidden: true }, + }, + { + path: "404", + component: () => import("@/views/error-page/404.vue"), + meta: { hidden: true }, + }, + ], + }, + + // 外部链接 + /*{ + path: '/external-link', + component: Layout, + children: [ + { + path: 'https://www.cnblogs.com/haoxianrui/', + meta: { title: '外部链接', icon: 'link' } + } + ] + }*/ + // 多级嵌套路由 + /* { + path: '/nested', + component: Layout, + redirect: '/nested/level1/level2', + name: 'Nested', + meta: {title: '多级菜单', icon: 'nested'}, + children: [ + { + path: 'level1', + component: () => import('@/views/nested/level1/index.vue'), + name: 'Level1', + meta: {title: '菜单一级'}, + redirect: '/nested/level1/level2', + children: [ + { + path: 'level2', + component: () => import('@/views/nested/level1/level2/index.vue'), + name: 'Level2', + meta: {title: '菜单二级'}, + redirect: '/nested/level1/level2/level3', + children: [ + { + path: 'level3-1', + component: () => import('@/views/nested/level1/level2/level3/index1.vue'), + name: 'Level3-1', + meta: {title: '菜单三级-1'} + }, + { + path: 'level3-2', + component: () => import('@/views/nested/level1/level2/level3/index2.vue'), + name: 'Level3-2', + meta: {title: '菜单三级-2'} + } + ] + } + ] + }, + ] + }*/ +]; + +/** + * 创建路由 + */ +const router = createRouter({ + history: createWebHashHistory(), + routes: constantRoutes as RouteRecordRaw[], + // 刷新时,滚动条位置还原 + scrollBehavior: () => ({ left: 0, top: 0 }), +}); + +/** + * 重置路由 + */ +export function resetRouter() { + router.replace({ path: "/login" }); +} + +export default router; diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..f8cbc42 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,62 @@ +// 系统设置 +interface DefaultSettings { + /** + * 系统title + */ + title: string; + + /** + * 是否显示设置 + */ + showSettings: boolean; + /** + * 是否显示多标签导航 + */ + tagsView: boolean; + /** + *是否固定头部 + */ + fixedHeader: boolean; + /** + * 是否显示侧边栏Logo + */ + sidebarLogo: boolean; + /** + * 导航栏布局 + */ + layout: string; + /** + * 主题模式 + */ + theme: string; + + /** + * 布局大小 + */ + size: string; + + /** + * 语言 + */ + language: string; +} + +const defaultSettings: DefaultSettings = { + title: "恒信高科应用平台", + showSettings: true, + tagsView: true, + fixedHeader: false, + sidebarLogo: true, + layout: "left", + /** + * 主题模式 + * + * dark:暗黑模式 + * light: 明亮模式 + */ + theme: "dark", + size: "default", // default |large |small + language: "zh-cn", // zh-cn| en +}; + +export default defaultSettings; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..ceb4219 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,11 @@ +import type { App } from 'vue'; +import { createPinia } from 'pinia'; + +const store = createPinia(); + +// 全局注册 store +export function setupStore(app: App) { + app.use(store); +} + +export { store }; diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..cd1c186 --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,85 @@ +import { defineStore } from "pinia"; +import { useStorage } from "@vueuse/core"; +import defaultSettings from "@/settings"; + +// 导入 Element Plus 中英文语言包 +import zhCn from "element-plus/es/locale/lang/zh-cn"; +import en from "element-plus/es/locale/lang/en"; + +// setup +export const useAppStore = defineStore("app", () => { + // state + const device = useStorage("device", "desktop"); + const size = useStorage("size", defaultSettings.size); + const language = useStorage("language", defaultSettings.language); + + const sidebarStatus = useStorage("sidebarStatus", "closed"); + const sidebar = reactive({ + opened: sidebarStatus.value !== "closed", + withoutAnimation: false, + }); + + /** + * 根据语言标识读取对应的语言包 + */ + const locale = computed(() => { + if (language?.value == "en") { + return en; + } else { + return zhCn; + } + }); + + // actions + function toggleSidebar(withoutAnimation: boolean) { + sidebar.opened = !sidebar.opened; + sidebar.withoutAnimation = withoutAnimation; + if (sidebar.opened) { + sidebarStatus.value = "opened"; + } else { + sidebarStatus.value = "closed"; + } + } + + function closeSideBar(withoutAnimation: boolean) { + sidebar.opened = false; + sidebar.withoutAnimation = withoutAnimation; + sidebarStatus.value = "closed"; + } + + function openSideBar(withoutAnimation: boolean) { + sidebar.opened = true; + sidebar.withoutAnimation = withoutAnimation; + sidebarStatus.value = "opened"; + } + + function toggleDevice(val: string) { + device.value = val; + } + + function changeSize(val: string) { + size.value = val; + } + /** + * 切换语言 + * + * @param val + */ + function changeLanguage(val: string) { + language.value = val; + } + + return { + device, + sidebar, + language, + locale, + size, + toggleDevice, + changeSize, + changeLanguage, + toggleSidebar, + closeSideBar, + openSideBar, + }; +}); diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..8a69655 --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,106 @@ +import { RouteRecordRaw } from "vue-router"; +import { defineStore } from "pinia"; +import { constantRoutes } from "@/router"; +import { store } from "@/store"; +import { listRoutes } from "@/api/menu"; + +const modules = import.meta.glob("../../views/**/**.vue"); +const Layout = () => import("@/layout/index.vue"); + +/** + * Use meta.role to determine if the current user has permission + * + * @param roles 用户角色集合 + * @param route 路由 + * @returns + */ +const hasPermission = (roles: string[], route: RouteRecordRaw) => { + if (route.meta && route.meta.roles) { + // 角色【超级管理员】拥有所有权限,忽略校验 + if (roles.includes("ROOT")) { + return true; + } + return roles.some((role) => { + if (route.meta?.roles !== undefined) { + return (route.meta.roles as string[]).includes(role); + } + }); + } + return false; +}; + +/** + * 递归过滤有权限的异步(动态)路由 + * + * @param routes 接口返回的异步(动态)路由 + * @param roles 用户角色集合 + * @returns 返回用户有权限的异步(动态)路由 + */ +const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => { + const asyncRoutes: RouteRecordRaw[] = []; + + routes.forEach((route) => { + const tmpRoute = { ...route }; // ES6扩展运算符复制新对象 + + // 判断用户(角色)是否有该路由的访问权限 + if (hasPermission(roles, tmpRoute)) { + if (tmpRoute.component?.toString() == "Layout") { + tmpRoute.component = Layout; + console.log(); + } else { + const component = modules[`../../views/${tmpRoute.component}.vue`]; + if (component) { + tmpRoute.component = component; + } else { + tmpRoute.component = modules[`../../views/error-page/404.vue`]; + } + } + + if (tmpRoute.children) { + tmpRoute.children = filterAsyncRoutes(tmpRoute.children, roles); + } + + asyncRoutes.push(tmpRoute); + } + }); + + return asyncRoutes; +}; + +// setup +export const usePermissionStore = defineStore("permission", () => { + // state + const routes = ref([]); + + // actions + function setRoutes(newRoutes: RouteRecordRaw[]) { + routes.value = constantRoutes.concat(newRoutes); + } + /** + * 生成动态路由 + * + * @param roles 用户角色集合 + * @returns + */ + function generateRoutes(roles: string[]) { + return new Promise((resolve, reject) => { + // 接口获取所有路由 + listRoutes() + .then(({ data: asyncRoutes }) => { + // 根据角色获取有访问权限的路由 + const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles); + setRoutes(accessedRoutes); + resolve(accessedRoutes); + }) + .catch((error) => { + reject(error); + }); + }); + } + return { routes, setRoutes, generateRoutes }; +}); + +// 非setup +export function usePermissionStoreHook() { + return usePermissionStore(store); +} diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts new file mode 100644 index 0000000..908359c --- /dev/null +++ b/src/store/modules/settings.ts @@ -0,0 +1,47 @@ +import { defineStore } from "pinia"; +import defaultSettings from "@/settings"; +import { useStorage } from "@vueuse/core"; + +export const useSettingsStore = defineStore("setting", () => { + // state + const tagsView = useStorage("tagsView", defaultSettings.tagsView); + + const showSettings = ref(defaultSettings.showSettings); + const fixedHeader = ref(defaultSettings.fixedHeader); + const sidebarLogo = ref(defaultSettings.sidebarLogo); + + const layout = useStorage("layout", defaultSettings.layout); + + // actions + function changeSetting(param: { key: string; value: any }) { + const { key, value } = param; + switch (key) { + case "showSettings": + showSettings.value = value; + break; + case "fixedHeader": + fixedHeader.value = value; + break; + case "tagsView": + tagsView.value = value; + break; + case "sidevarLogo": + sidebarLogo.value = value; + break; + case "layout": + layout.value = value; + break; + default: + break; + } + } + + return { + showSettings, + tagsView, + fixedHeader, + sidebarLogo, + layout, + changeSetting, + }; +}); diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts new file mode 100644 index 0000000..93c09a5 --- /dev/null +++ b/src/store/modules/tagsView.ts @@ -0,0 +1,218 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; +import { RouteLocationNormalized } from "vue-router"; + +export interface TagView extends Partial { + title?: string; +} + +// setup +export const useTagsViewStore = defineStore("tagsView", () => { + // state + const visitedViews = ref([]); + const cachedViews = ref([]); + + // actions + function addVisitedView(view: TagView) { + if (visitedViews.value.some((v) => v.path === view.path)) return; + if (view.meta && view.meta.affix) { + visitedViews.value.unshift( + Object.assign({}, view, { + title: view.meta?.title || "no-name", + }) + ); + } else { + visitedViews.value.push( + Object.assign({}, view, { + title: view.meta?.title || "no-name", + }) + ); + } + } + + function addCachedView(view: TagView) { + const viewName = view.name as string; + if (cachedViews.value.includes(viewName)) return; + if (view.meta?.keepAlive) { + cachedViews.value.push(viewName); + } + } + + function delVisitedView(view: TagView) { + return new Promise((resolve) => { + for (const [i, v] of visitedViews.value.entries()) { + if (v.path === view.path) { + visitedViews.value.splice(i, 1); + break; + } + } + resolve([...visitedViews.value]); + }); + } + + function delCachedView(view: TagView) { + const viewName = view.name as string; + return new Promise((resolve) => { + const index = cachedViews.value.indexOf(viewName); + index > -1 && cachedViews.value.splice(index, 1); + resolve([...cachedViews.value]); + }); + } + + function delOtherVisitedViews(view: TagView) { + return new Promise((resolve) => { + visitedViews.value = visitedViews.value.filter((v) => { + return v.meta?.affix || v.path === view.path; + }); + resolve([...visitedViews.value]); + }); + } + + function delOtherCachedViews(view: TagView) { + const viewName = view.name as string; + return new Promise((resolve) => { + const index = cachedViews.value.indexOf(viewName); + if (index > -1) { + cachedViews.value = cachedViews.value.slice(index, index + 1); + } else { + // if index = -1, there is no cached tags + cachedViews.value = []; + } + resolve([...cachedViews.value]); + }); + } + + function updateVisitedView(view: TagView) { + for (let v of visitedViews.value) { + if (v.path === view.path) { + v = Object.assign(v, view); + break; + } + } + } + + function addView(view: TagView) { + addVisitedView(view); + addCachedView(view); + } + + function delView(view: TagView) { + return new Promise((resolve) => { + delVisitedView(view); + delCachedView(view); + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value], + }); + }); + } + + function delOtherViews(view: TagView) { + return new Promise((resolve) => { + delOtherVisitedViews(view); + delOtherCachedViews(view); + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value], + }); + }); + } + + function delLeftViews(view: TagView) { + return new Promise((resolve) => { + const currIndex = visitedViews.value.findIndex( + (v) => v.path === view.path + ); + if (currIndex === -1) { + return; + } + visitedViews.value = visitedViews.value.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index >= currIndex || (item.meta && item.meta.affix)) { + return true; + } + + const cacheIndex = cachedViews.value.indexOf(item.name as string); + if (cacheIndex > -1) { + cachedViews.value.splice(cacheIndex, 1); + } + return false; + }); + resolve({ + visitedViews: [...visitedViews.value], + }); + }); + } + function delRightViews(view: TagView) { + return new Promise((resolve) => { + const currIndex = visitedViews.value.findIndex( + (v) => v.path === view.path + ); + if (currIndex === -1) { + return; + } + visitedViews.value = visitedViews.value.filter((item, index) => { + // affix:true 固定tag,例如“首页” + if (index <= currIndex || (item.meta && item.meta.affix)) { + return true; + } + + const cacheIndex = cachedViews.value.indexOf(item.name as string); + if (cacheIndex > -1) { + cachedViews.value.splice(cacheIndex, 1); + } + return false; + }); + resolve({ + visitedViews: [...visitedViews.value], + }); + }); + } + + function delAllViews() { + return new Promise((resolve) => { + const affixTags = visitedViews.value.filter((tag) => tag.meta?.affix); + visitedViews.value = affixTags; + cachedViews.value = []; + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value], + }); + }); + } + + function delAllVisitedViews() { + return new Promise((resolve) => { + const affixTags = visitedViews.value.filter((tag) => tag.meta?.affix); + visitedViews.value = affixTags; + resolve([...visitedViews.value]); + }); + } + + function delAllCachedViews() { + return new Promise((resolve) => { + cachedViews.value = []; + resolve([...cachedViews.value]); + }); + } + + return { + visitedViews, + cachedViews, + addVisitedView, + addCachedView, + delVisitedView, + delCachedView, + delOtherVisitedViews, + delOtherCachedViews, + updateVisitedView, + addView, + delView, + delOtherViews, + delLeftViews, + delRightViews, + delAllViews, + delAllVisitedViews, + delAllCachedViews, + }; +}); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..b011d6e --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,128 @@ +import { defineStore } from "pinia"; + +import { loginApi, logoutApi,loginApiIng } from "@/api/auth"; +import { getUserInfo,getUserInfoIng } from "@/api/user"; +import { resetRouter } from "@/router"; +import { store } from "@/store"; + +import { LoginData } from "@/api/auth/types"; +import { UserInfo } from "@/api/user/types"; + +import { useStorage } from "@vueuse/core"; + +export const useUserStore = defineStore("user", () => { + // state + const tokenIng = useStorage("accessToken", ""); + const nickname = ref(""); + const avatar = ref(""); + const roles = ref>([]); // 用户角色编码集合 → 判断路由权限 + const perms = ref>([]); // 用户权限编码集合 → 判断按钮权限 + const userKey = useStorage("userKey", ""); + const userToken = useStorage("userToken", ""); + /** + * 登录调用 + * + * @param {LoginData} + * @returns + */ + function login(loginData: LoginData) { + return new Promise((resolve, reject) => { + loginApiIng(loginData) + .then((response) => { + const { tokenType, accessToken, key, token } = response.data; + tokenIng.value = tokenType + " " + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx + userKey.value = key; + userToken.value = token; + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); + } + + // 获取信息(用户昵称、头像、角色集合、权限集合) + function getInfo() { + return new Promise((resolve, reject) => { + // getUserInfo() + // .then(({ data }) => { + // if (!data) { + // return reject("验证失败,请重新登录。"); + // } + // if (!data.roles || data.roles.length <= 0) { + // reject("获取用户信息:角色必须是非null数组!"); + // } + // nickname.value = data.nickname; + // avatar.value = data.avatar; + // roles.value = data.roles; + // perms.value = data.perms; + // resolve(data); + // }) + // .catch((error) => { + // reject(error); + // }); + getUserInfoIng() + .then(({data})=>{ + if (!data) { + return reject("验证失败,请重新登录。"); + } + if (!data.roles || data.roles.length <= 0) { + reject("获取用户信息:角色必须是非null数组!"); + } + nickname.value = data.nickname; + avatar.value = data.avatar; + roles.value = data.roles; + perms.value = data.perms; + resolve(data); + }) + .catch((error) => { + reject(error); + }); + }); + } + + // 注销 + function logout() { + return new Promise((resolve, reject) => { + logoutApi() + .then(() => { + resetRouter(); + resetToken(); + location.reload(); // 清空路由 + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); + } + + // 重置 + function resetToken() { + tokenIng.value = ""; + nickname.value = ""; + userKey.value = ""; + userToken.value = ""; + avatar.value = ""; + roles.value = []; + perms.value = []; + } + return { + tokenIng, + userKey, + userToken, + nickname, + avatar, + roles, + perms, + login, + getInfo, + logout, + resetToken, + }; +}); + +// 非setup +export function useUserStoreHook() { + return useUserStore(store); +} diff --git a/src/styles/dark.scss b/src/styles/dark.scss new file mode 100644 index 0000000..5b719ff --- /dev/null +++ b/src/styles/dark.scss @@ -0,0 +1,33 @@ +html.dark { + --menuBg: var(--el-bg-color-overlay); + --menuText: #fff; + --menuActiveText: var(--el-menu-active-color); + --menuHover: rgb(0 0 0 / 20%); + --subMenuBg: var(--el-menu-bg-color); + --subMenuActiveText: var(--el-menu-active-color); + --subMenuHover: rgb(0 0 0 / 20%); + + .navbar { + color: var(--el-text-color-regular); + background-color: var(--el-bg-color); + + .setting-container .setting-item:hover { + background: var(--el-fill-color-light); + } + } + + .right-panel-btn { + background-color: var(--el-color-primary-dark); + } + + .svg-icon, + svg { + fill: var(--el-text-color-regular); + } + + .sidebar-container { + .el-menu-item.is-active .svg-icon { + fill: var(--el-color-primary); + } + } +} diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..655cc38 --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,16 @@ +@import "./sidebar"; +@import "./reset"; +@import "./dark"; + +.app-container { + margin: 20px; + + .search { + padding: 18px 0 0 10px; + margin-bottom: 10px; + background-color: var(--el-bg-color-overlay); + border: 1px solid var(--el-border-color-light); + border-radius: 4px; + box-shadow: var(--el-box-shadow-light); + } +} diff --git a/src/styles/reset.scss b/src/styles/reset.scss new file mode 100644 index 0000000..9b19e4c --- /dev/null +++ b/src/styles/reset.scss @@ -0,0 +1,75 @@ +*, +::before, +::after { + box-sizing: border-box; + border-color: currentcolor; + border-style: solid; + border-width: 0; +} + +#app { + width: 100%; + height: 100%; +} + +html { + box-sizing: border-box; + width: 100%; + height: 100%; + line-height: 1.5; + tab-size: 4; + text-size-adjust: 100%; +} + +body { + width: 100%; + height: 100%; + margin: 0; + font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", + "Microsoft YaHei", "微软雅黑", Arial, sans-serif; + line-height: inherit; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizelegibility; +} + +a { + color: inherit; + text-decoration: inherit; +} + +img, +svg { + display: inline-block; +} + +svg { + vertical-align: -0.15em; //因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 +} + +ul, +li { + padding: 0; + margin: 0; + list-style: none; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +a, +a:focus, +a:hover { + color: inherit; + text-decoration: none; + cursor: pointer; +} + +a:focus, +a:active, +div:focus { + outline: none; +} diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss new file mode 100644 index 0000000..efaa4e9 --- /dev/null +++ b/src/styles/sidebar.scss @@ -0,0 +1,205 @@ +#app { + .main-container { + position: relative; + min-height: 100%; + margin-left: $sideBarWidth; + transition: margin-left 0.28s; + } + + .sidebar-container { + position: fixed; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + width: $sideBarWidth !important; + height: 100%; + overflow: hidden; + background-color: $menuBg; + transition: width 0.28s; + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, + 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + .svg-icon { + margin-right: 16px; + } + + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + + .el-menu { + width: 100% !important; + height: 100%; + border: none; + } + + // menu hover + .el-sub-menu__title { + &:hover { + background-color: $menuHover !important; + } + } + + .is-active > .el-sub-menu__title { + color: $subMenuActiveText !important; + } + + & .nest-menu .el-sub-menu > .el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: $sideBarWidth !important; + background-color: $subMenuBg !important; + + &:hover { + background-color: $subMenuHover !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + + .svg-icon { + margin-right: 0; + } + } + + .main-container { + margin-left: 54px; + } + + .el-sub-menu { + overflow: hidden; + + & > .el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + + .sub-el-icon { + margin-left: 19px; + } + + .el-sub-menu__icon-arrow { + display: none; + } + } + } + + .el-menu--collapse { + .el-sub-menu { + & > .el-sub-menu__title { + & > span { + display: inline-block; + width: 0; + height: 0; + overflow: hidden; + visibility: hidden; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: $sideBarWidth !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0; + } + + .sidebar-container { + width: $sideBarWidth !important; + transition: transform 0.28s; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$sideBarWidth, 0, 0); + } + } + } + + .withoutAnimation { + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + & > .el-menu { + .svg-icon { + margin-right: 16px; + } + + .sub-el-icon { + margin-right: 12px; + margin-left: -2px; + } + } + + .nest-menu .el-sub-menu > .el-sub-menu__title, + .el-menu-item { + &:hover { + // you can use $subMenuHover + background-color: $menuHover !important; + } + } + + // the scroll bar appears when the subMenu is too long + > .el-menu--popup { + max-height: 100vh; + overflow-y: auto; + + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } + } +} diff --git a/src/styles/variables.module.scss b/src/styles/variables.module.scss new file mode 100644 index 0000000..cbc114a --- /dev/null +++ b/src/styles/variables.module.scss @@ -0,0 +1,6 @@ +// 导出 variables.module.scss 变量提供给TypeScript使用 +:export { + menuBg: $menuBg; + menuText: $menuText; + menuActiveText: $menuActiveText +} \ No newline at end of file diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..e11df30 --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,22 @@ +// 全局SCSS变量 + +:root { + --menuBg: #304156; + --menuText: #bfcbd9; + --menuActiveText: #409eff; + --menuHover: #263445; + --subMenuBg: #1f2d3d; + --subMenuActiveText: #f4f4f5; + --subMenuHover: #001528; +} + +$menuBg: var(--menuBg); +$menuText: var(--menuText); +$menuActiveText: var(--menuActiveText); +$menuHover: var(--menuHover); + +$subMenuBg: var(--subMenuBg); +$subMenuActiveText: var(--subMenuActiveText); +$subMenuHover: var(--subMenuHover); + +$sideBarWidth: 210px; diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts new file mode 100644 index 0000000..a8cd9d6 --- /dev/null +++ b/src/types/auto-imports.d.ts @@ -0,0 +1,841 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const ElForm: typeof import('element-plus/es')['ElForm'] + const ElMessage: typeof import('element-plus/es')['ElMessage'] + const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] + const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] + const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] + const computed: typeof import('vue')['computed'] + const computedAsync: typeof import('@vueuse/core')['computedAsync'] + const computedEager: typeof import('@vueuse/core')['computedEager'] + const computedInject: typeof import('@vueuse/core')['computedInject'] + const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] + const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] + const controlledRef: typeof import('@vueuse/core')['controlledRef'] + const createApp: typeof import('vue')['createApp'] + const createEventHook: typeof import('@vueuse/core')['createEventHook'] + const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] + const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] + const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] + const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] + const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] + const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] + const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] + const customRef: typeof import('vue')['customRef'] + const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] + const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] + const effectScope: typeof import('vue')['effectScope'] + const extendRef: typeof import('@vueuse/core')['extendRef'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] + const inject: typeof import('vue')['inject'] + const isDefined: typeof import('@vueuse/core')['isDefined'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] + const onLongPress: typeof import('@vueuse/core')['onLongPress'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] + const provide: typeof import('vue')['provide'] + const reactify: typeof import('@vueuse/core')['reactify'] + const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] + const reactive: typeof import('vue')['reactive'] + const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] + const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] + const reactivePick: typeof import('@vueuse/core')['reactivePick'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] + const refDebounced: typeof import('@vueuse/core')['refDebounced'] + const refDefault: typeof import('@vueuse/core')['refDefault'] + const refThrottled: typeof import('@vueuse/core')['refThrottled'] + const refWithControl: typeof import('@vueuse/core')['refWithControl'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const resolveRef: typeof import('@vueuse/core')['resolveRef'] + const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + 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 useCloned: typeof import('@vueuse/core')['useCloned'] + const useColorMode: typeof import('@vueuse/core')['useColorMode'] + const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] + const useCounter: typeof import('@vueuse/core')['useCounter'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVar: typeof import('@vueuse/core')['useCssVar'] + const useCssVars: typeof import('vue')['useCssVars'] + const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] + const useCycleList: typeof import('@vueuse/core')['useCycleList'] + const useDark: typeof import('@vueuse/core')['useDark'] + const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] + const useDebounce: typeof import('@vueuse/core')['useDebounce'] + const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] + const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] + const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] + const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] + const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] + const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] + const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] + const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] + const useDraggable: typeof import('@vueuse/core')['useDraggable'] + const useDropZone: typeof import('@vueuse/core')['useDropZone'] + const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] + const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] + const useElementHover: typeof import('@vueuse/core')['useElementHover'] + const useElementSize: typeof import('@vueuse/core')['useElementSize'] + const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] + const useEventBus: typeof import('@vueuse/core')['useEventBus'] + const useEventListener: typeof import('@vueuse/core')['useEventListener'] + const useEventSource: typeof import('@vueuse/core')['useEventSource'] + const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] + const useFavicon: typeof import('@vueuse/core')['useFavicon'] + const useFetch: typeof import('@vueuse/core')['useFetch'] + const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] + const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] + const useFocus: typeof import('@vueuse/core')['useFocus'] + const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] + const useFps: typeof import('@vueuse/core')['useFps'] + const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] + const useGamepad: typeof import('@vueuse/core')['useGamepad'] + const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] + const 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 useMounted: typeof import('@vueuse/core')['useMounted'] + const useMouse: typeof import('@vueuse/core')['useMouse'] + const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] + const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] + const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] + const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] + const useNetwork: typeof import('@vueuse/core')['useNetwork'] + const useNow: typeof import('@vueuse/core')['useNow'] + const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] + const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] + const useOnline: typeof import('@vueuse/core')['useOnline'] + const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] + const useParallax: typeof import('@vueuse/core')['useParallax'] + const useParentElement: typeof import('@vueuse/core')['useParentElement'] + const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] + const usePermission: typeof import('@vueuse/core')['usePermission'] + const usePointer: typeof import('@vueuse/core')['usePointer'] + const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] + const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] + const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] + const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] + const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] + const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] + const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] + const usePrevious: typeof import('@vueuse/core')['usePrevious'] + const useRafFn: typeof import('@vueuse/core')['useRafFn'] + const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] + const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] + const 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 useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] + const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] + const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] + const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] + const useThrottle: typeof import('@vueuse/core')['useThrottle'] + const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] + const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] + const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] + const useTimeout: typeof import('@vueuse/core')['useTimeout'] + const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] + const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] + const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] + const useTitle: typeof import('@vueuse/core')['useTitle'] + const useToNumber: typeof import('@vueuse/core')['useToNumber'] + const useToString: typeof import('@vueuse/core')['useToString'] + const useToggle: typeof import('@vueuse/core')['useToggle'] + const useTransition: typeof import('@vueuse/core')['useTransition'] + const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] + const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] + const useVModel: typeof import('@vueuse/core')['useVModel'] + const useVModels: typeof import('@vueuse/core')['useVModels'] + const useVibrate: typeof import('@vueuse/core')['useVibrate'] + const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] + const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] + const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] + const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] + const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] + const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] + const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] + const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] + const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] + const watch: typeof import('vue')['watch'] + const watchArray: typeof import('@vueuse/core')['watchArray'] + const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] + const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] + const watchDeep: typeof import('@vueuse/core')['watchDeep'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] + const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] + const watchOnce: typeof import('@vueuse/core')['watchOnce'] + const watchPausable: typeof import('@vueuse/core')['watchPausable'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] + const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] + const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] + const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] + const whenever: typeof import('@vueuse/core')['whenever'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' +} +// for vue template auto import +import { UnwrapRef } from 'vue' +declare module 'vue' { + interface ComponentCustomProperties { + readonly EffectScope: UnwrapRef + readonly ElForm: UnwrapRef + readonly ElMessage: UnwrapRef + readonly ElMessageBox: UnwrapRef + readonly asyncComputed: UnwrapRef + readonly autoResetRef: UnwrapRef + readonly computed: UnwrapRef + readonly computedAsync: UnwrapRef + readonly computedEager: UnwrapRef + readonly computedInject: UnwrapRef + readonly computedWithControl: UnwrapRef + readonly controlledComputed: UnwrapRef + readonly controlledRef: UnwrapRef + readonly createApp: UnwrapRef + readonly createEventHook: UnwrapRef + readonly createGlobalState: UnwrapRef + readonly createInjectionState: UnwrapRef + readonly createReactiveFn: UnwrapRef + readonly createReusableTemplate: UnwrapRef + readonly createSharedComposable: UnwrapRef + readonly createTemplatePromise: UnwrapRef + readonly createUnrefFn: UnwrapRef + readonly customRef: UnwrapRef + readonly debouncedRef: UnwrapRef + readonly debouncedWatch: UnwrapRef + readonly defineAsyncComponent: UnwrapRef + readonly defineComponent: UnwrapRef + readonly eagerComputed: UnwrapRef + readonly effectScope: UnwrapRef + readonly extendRef: UnwrapRef + readonly getCurrentInstance: UnwrapRef + readonly getCurrentScope: UnwrapRef + readonly h: UnwrapRef + readonly ignorableWatch: UnwrapRef + readonly inject: UnwrapRef + readonly isDefined: UnwrapRef + readonly isProxy: UnwrapRef + readonly isReactive: UnwrapRef + readonly isReadonly: UnwrapRef + readonly isRef: UnwrapRef + readonly makeDestructurable: UnwrapRef + readonly markRaw: UnwrapRef + readonly nextTick: UnwrapRef + readonly onActivated: UnwrapRef + readonly onBeforeMount: UnwrapRef + readonly onBeforeUnmount: UnwrapRef + readonly onBeforeUpdate: UnwrapRef + readonly onClickOutside: UnwrapRef + readonly onDeactivated: UnwrapRef + readonly onErrorCaptured: UnwrapRef + readonly onKeyStroke: UnwrapRef + readonly onLongPress: UnwrapRef + readonly onMounted: UnwrapRef + readonly onRenderTracked: UnwrapRef + readonly onRenderTriggered: UnwrapRef + readonly onScopeDispose: UnwrapRef + readonly onServerPrefetch: UnwrapRef + readonly onStartTyping: UnwrapRef + readonly onUnmounted: UnwrapRef + readonly onUpdated: UnwrapRef + readonly pausableWatch: UnwrapRef + readonly provide: UnwrapRef + readonly reactify: UnwrapRef + readonly reactifyObject: UnwrapRef + readonly reactive: UnwrapRef + readonly reactiveComputed: UnwrapRef + readonly reactiveOmit: UnwrapRef + readonly reactivePick: UnwrapRef + readonly readonly: UnwrapRef + readonly ref: UnwrapRef + readonly refAutoReset: UnwrapRef + readonly refDebounced: UnwrapRef + readonly refDefault: UnwrapRef + readonly refThrottled: UnwrapRef + readonly refWithControl: UnwrapRef + readonly resolveComponent: UnwrapRef + readonly resolveRef: UnwrapRef + readonly resolveUnref: UnwrapRef + readonly shallowReactive: UnwrapRef + readonly shallowReadonly: UnwrapRef + readonly shallowRef: UnwrapRef + readonly syncRef: UnwrapRef + readonly syncRefs: UnwrapRef + readonly templateRef: UnwrapRef + readonly throttledRef: UnwrapRef + readonly throttledWatch: UnwrapRef + readonly toRaw: UnwrapRef + readonly toReactive: UnwrapRef + readonly toRef: UnwrapRef + readonly toRefs: UnwrapRef + readonly toValue: UnwrapRef + readonly triggerRef: UnwrapRef + readonly tryOnBeforeMount: UnwrapRef + readonly tryOnBeforeUnmount: UnwrapRef + readonly tryOnMounted: UnwrapRef + readonly tryOnScopeDispose: UnwrapRef + readonly tryOnUnmounted: UnwrapRef + readonly unref: UnwrapRef + readonly unrefElement: UnwrapRef + readonly until: UnwrapRef + readonly useActiveElement: UnwrapRef + readonly useAnimate: UnwrapRef + readonly useArrayDifference: UnwrapRef + readonly useArrayEvery: UnwrapRef + readonly useArrayFilter: UnwrapRef + readonly useArrayFind: UnwrapRef + readonly useArrayFindIndex: UnwrapRef + readonly useArrayFindLast: UnwrapRef + readonly useArrayIncludes: UnwrapRef + readonly useArrayJoin: UnwrapRef + readonly useArrayMap: UnwrapRef + readonly useArrayReduce: UnwrapRef + readonly useArraySome: UnwrapRef + readonly useArrayUnique: UnwrapRef + readonly useAsyncQueue: UnwrapRef + readonly useAsyncState: UnwrapRef + readonly useAttrs: UnwrapRef + readonly useBase64: UnwrapRef + readonly useBattery: UnwrapRef + readonly useBluetooth: UnwrapRef + readonly useBreakpoints: UnwrapRef + readonly useBroadcastChannel: UnwrapRef + readonly useBrowserLocation: UnwrapRef + readonly useCached: UnwrapRef + readonly useClipboard: UnwrapRef + readonly useCloned: UnwrapRef + readonly useColorMode: UnwrapRef + readonly useConfirmDialog: UnwrapRef + readonly useCounter: UnwrapRef + readonly useCssModule: UnwrapRef + readonly useCssVar: UnwrapRef + readonly useCssVars: UnwrapRef + readonly useCurrentElement: UnwrapRef + readonly useCycleList: UnwrapRef + readonly useDark: UnwrapRef + readonly useDateFormat: UnwrapRef + readonly useDebounce: UnwrapRef + readonly useDebounceFn: UnwrapRef + readonly useDebouncedRefHistory: UnwrapRef + readonly useDeviceMotion: UnwrapRef + readonly useDeviceOrientation: UnwrapRef + readonly useDevicePixelRatio: UnwrapRef + readonly useDevicesList: UnwrapRef + readonly useDisplayMedia: UnwrapRef + readonly useDocumentVisibility: UnwrapRef + readonly useDraggable: UnwrapRef + readonly useDropZone: UnwrapRef + readonly useElementBounding: UnwrapRef + readonly useElementByPoint: UnwrapRef + readonly useElementHover: UnwrapRef + readonly useElementSize: UnwrapRef + readonly useElementVisibility: UnwrapRef + readonly useEventBus: UnwrapRef + readonly useEventListener: UnwrapRef + readonly useEventSource: UnwrapRef + readonly useEyeDropper: UnwrapRef + readonly useFavicon: UnwrapRef + readonly useFetch: UnwrapRef + readonly useFileDialog: UnwrapRef + readonly useFileSystemAccess: UnwrapRef + readonly useFocus: UnwrapRef + readonly useFocusWithin: UnwrapRef + readonly useFps: UnwrapRef + readonly useFullscreen: UnwrapRef + readonly useGamepad: UnwrapRef + readonly useGeolocation: UnwrapRef + readonly useIdle: UnwrapRef + readonly useImage: UnwrapRef + readonly useInfiniteScroll: UnwrapRef + readonly useIntersectionObserver: UnwrapRef + readonly useInterval: UnwrapRef + readonly useIntervalFn: UnwrapRef + readonly useKeyModifier: UnwrapRef + readonly useLastChanged: UnwrapRef + readonly useLocalStorage: UnwrapRef + readonly useMagicKeys: UnwrapRef + readonly useManualRefHistory: UnwrapRef + readonly useMediaControls: UnwrapRef + readonly useMediaQuery: UnwrapRef + readonly useMemoize: UnwrapRef + readonly useMemory: UnwrapRef + readonly useMounted: UnwrapRef + readonly useMouse: UnwrapRef + readonly useMouseInElement: UnwrapRef + readonly useMousePressed: UnwrapRef + readonly useMutationObserver: UnwrapRef + readonly useNavigatorLanguage: UnwrapRef + readonly useNetwork: UnwrapRef + readonly useNow: UnwrapRef + readonly useObjectUrl: UnwrapRef + readonly useOffsetPagination: UnwrapRef + readonly useOnline: UnwrapRef + readonly usePageLeave: UnwrapRef + readonly useParallax: UnwrapRef + readonly useParentElement: UnwrapRef + readonly usePerformanceObserver: UnwrapRef + readonly usePermission: UnwrapRef + readonly usePointer: UnwrapRef + readonly usePointerLock: UnwrapRef + readonly usePointerSwipe: UnwrapRef + readonly usePreferredColorScheme: UnwrapRef + readonly usePreferredContrast: UnwrapRef + readonly usePreferredDark: UnwrapRef + readonly usePreferredLanguages: UnwrapRef + readonly usePreferredReducedMotion: UnwrapRef + readonly usePrevious: UnwrapRef + readonly useRafFn: UnwrapRef + readonly useRefHistory: UnwrapRef + readonly useResizeObserver: UnwrapRef + readonly useScreenOrientation: UnwrapRef + readonly useScreenSafeArea: UnwrapRef + readonly useScriptTag: UnwrapRef + readonly useScroll: UnwrapRef + readonly useScrollLock: UnwrapRef + readonly useSessionStorage: UnwrapRef + readonly useShare: UnwrapRef + readonly useSlots: UnwrapRef + readonly useSorted: UnwrapRef + readonly useSpeechRecognition: UnwrapRef + readonly useSpeechSynthesis: UnwrapRef + readonly useStepper: UnwrapRef + readonly useStorage: UnwrapRef + readonly useStorageAsync: UnwrapRef + readonly useStyleTag: UnwrapRef + readonly useSupported: UnwrapRef + readonly useSwipe: UnwrapRef + readonly useTemplateRefsList: UnwrapRef + readonly useTextDirection: UnwrapRef + readonly useTextSelection: UnwrapRef + readonly useTextareaAutosize: UnwrapRef + readonly useThrottle: UnwrapRef + readonly useThrottleFn: UnwrapRef + readonly useThrottledRefHistory: UnwrapRef + readonly useTimeAgo: UnwrapRef + readonly useTimeout: UnwrapRef + readonly useTimeoutFn: UnwrapRef + readonly useTimeoutPoll: UnwrapRef + readonly useTimestamp: UnwrapRef + readonly useTitle: UnwrapRef + readonly useToNumber: UnwrapRef + readonly useToString: UnwrapRef + readonly useToggle: UnwrapRef + readonly useTransition: UnwrapRef + readonly useUrlSearchParams: UnwrapRef + readonly useUserMedia: UnwrapRef + readonly useVModel: UnwrapRef + readonly useVModels: UnwrapRef + readonly useVibrate: UnwrapRef + readonly useVirtualList: UnwrapRef + readonly useWakeLock: UnwrapRef + readonly useWebNotification: UnwrapRef + readonly useWebSocket: UnwrapRef + readonly useWebWorker: UnwrapRef + readonly useWebWorkerFn: UnwrapRef + readonly useWindowFocus: UnwrapRef + readonly useWindowScroll: UnwrapRef + readonly useWindowSize: UnwrapRef + readonly watch: UnwrapRef + readonly watchArray: UnwrapRef + readonly watchAtMost: UnwrapRef + readonly watchDebounced: UnwrapRef + readonly watchDeep: UnwrapRef + readonly watchEffect: UnwrapRef + readonly watchIgnorable: UnwrapRef + readonly watchImmediate: UnwrapRef + readonly watchOnce: UnwrapRef + readonly watchPausable: UnwrapRef + readonly watchPostEffect: UnwrapRef + readonly watchSyncEffect: UnwrapRef + readonly watchThrottled: UnwrapRef + readonly watchTriggerable: UnwrapRef + readonly watchWithFilter: UnwrapRef + readonly whenever: UnwrapRef + } +} +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + readonly EffectScope: UnwrapRef + readonly ElForm: UnwrapRef + readonly ElMessage: UnwrapRef + readonly ElMessageBox: UnwrapRef + readonly asyncComputed: UnwrapRef + readonly autoResetRef: UnwrapRef + readonly computed: UnwrapRef + readonly computedAsync: UnwrapRef + readonly computedEager: UnwrapRef + readonly computedInject: UnwrapRef + readonly computedWithControl: UnwrapRef + readonly controlledComputed: UnwrapRef + readonly controlledRef: UnwrapRef + readonly createApp: UnwrapRef + readonly createEventHook: UnwrapRef + readonly createGlobalState: UnwrapRef + readonly createInjectionState: UnwrapRef + readonly createReactiveFn: UnwrapRef + readonly createReusableTemplate: UnwrapRef + readonly createSharedComposable: UnwrapRef + readonly createTemplatePromise: UnwrapRef + readonly createUnrefFn: UnwrapRef + readonly customRef: UnwrapRef + readonly debouncedRef: UnwrapRef + readonly debouncedWatch: UnwrapRef + readonly defineAsyncComponent: UnwrapRef + readonly defineComponent: UnwrapRef + readonly eagerComputed: UnwrapRef + readonly effectScope: UnwrapRef + readonly extendRef: UnwrapRef + readonly getCurrentInstance: UnwrapRef + readonly getCurrentScope: UnwrapRef + readonly h: UnwrapRef + readonly ignorableWatch: UnwrapRef + readonly inject: UnwrapRef + readonly isDefined: UnwrapRef + readonly isProxy: UnwrapRef + readonly isReactive: UnwrapRef + readonly isReadonly: UnwrapRef + readonly isRef: UnwrapRef + readonly makeDestructurable: UnwrapRef + readonly markRaw: UnwrapRef + readonly nextTick: UnwrapRef + readonly onActivated: UnwrapRef + readonly onBeforeMount: UnwrapRef + readonly onBeforeUnmount: UnwrapRef + readonly onBeforeUpdate: UnwrapRef + readonly onClickOutside: UnwrapRef + readonly onDeactivated: UnwrapRef + readonly onErrorCaptured: UnwrapRef + readonly onKeyStroke: UnwrapRef + readonly onLongPress: UnwrapRef + readonly onMounted: UnwrapRef + readonly onRenderTracked: UnwrapRef + readonly onRenderTriggered: UnwrapRef + readonly onScopeDispose: UnwrapRef + readonly onServerPrefetch: UnwrapRef + readonly onStartTyping: UnwrapRef + readonly onUnmounted: UnwrapRef + readonly onUpdated: UnwrapRef + readonly pausableWatch: UnwrapRef + readonly provide: UnwrapRef + readonly reactify: UnwrapRef + readonly reactifyObject: UnwrapRef + readonly reactive: UnwrapRef + readonly reactiveComputed: UnwrapRef + readonly reactiveOmit: UnwrapRef + readonly reactivePick: UnwrapRef + readonly readonly: UnwrapRef + readonly ref: UnwrapRef + readonly refAutoReset: UnwrapRef + readonly refDebounced: UnwrapRef + readonly refDefault: UnwrapRef + readonly refThrottled: UnwrapRef + readonly refWithControl: UnwrapRef + readonly resolveComponent: UnwrapRef + readonly resolveRef: UnwrapRef + readonly resolveUnref: UnwrapRef + readonly shallowReactive: UnwrapRef + readonly shallowReadonly: UnwrapRef + readonly shallowRef: UnwrapRef + readonly syncRef: UnwrapRef + readonly syncRefs: UnwrapRef + readonly templateRef: UnwrapRef + readonly throttledRef: UnwrapRef + readonly throttledWatch: UnwrapRef + readonly toRaw: UnwrapRef + readonly toReactive: UnwrapRef + readonly toRef: UnwrapRef + readonly toRefs: UnwrapRef + readonly toValue: UnwrapRef + readonly triggerRef: UnwrapRef + readonly tryOnBeforeMount: UnwrapRef + readonly tryOnBeforeUnmount: UnwrapRef + readonly tryOnMounted: UnwrapRef + readonly tryOnScopeDispose: UnwrapRef + readonly tryOnUnmounted: UnwrapRef + readonly unref: UnwrapRef + readonly unrefElement: UnwrapRef + readonly until: UnwrapRef + readonly useActiveElement: UnwrapRef + readonly useAnimate: UnwrapRef + readonly useArrayDifference: UnwrapRef + readonly useArrayEvery: UnwrapRef + readonly useArrayFilter: UnwrapRef + readonly useArrayFind: UnwrapRef + readonly useArrayFindIndex: UnwrapRef + readonly useArrayFindLast: UnwrapRef + readonly useArrayIncludes: UnwrapRef + readonly useArrayJoin: UnwrapRef + readonly useArrayMap: UnwrapRef + readonly useArrayReduce: UnwrapRef + readonly useArraySome: UnwrapRef + readonly useArrayUnique: UnwrapRef + readonly useAsyncQueue: UnwrapRef + readonly useAsyncState: UnwrapRef + readonly useAttrs: UnwrapRef + readonly useBase64: UnwrapRef + readonly useBattery: UnwrapRef + readonly useBluetooth: UnwrapRef + readonly useBreakpoints: UnwrapRef + readonly useBroadcastChannel: UnwrapRef + readonly useBrowserLocation: UnwrapRef + readonly useCached: UnwrapRef + readonly useClipboard: UnwrapRef + readonly useCloned: UnwrapRef + readonly useColorMode: UnwrapRef + readonly useConfirmDialog: UnwrapRef + readonly useCounter: UnwrapRef + readonly useCssModule: UnwrapRef + readonly useCssVar: UnwrapRef + readonly useCssVars: UnwrapRef + readonly useCurrentElement: UnwrapRef + readonly useCycleList: UnwrapRef + readonly useDark: UnwrapRef + readonly useDateFormat: UnwrapRef + readonly useDebounce: UnwrapRef + readonly useDebounceFn: UnwrapRef + readonly useDebouncedRefHistory: UnwrapRef + readonly useDeviceMotion: UnwrapRef + readonly useDeviceOrientation: UnwrapRef + readonly useDevicePixelRatio: UnwrapRef + readonly useDevicesList: UnwrapRef + readonly useDisplayMedia: UnwrapRef + readonly useDocumentVisibility: UnwrapRef + readonly useDraggable: UnwrapRef + readonly useDropZone: UnwrapRef + readonly useElementBounding: UnwrapRef + readonly useElementByPoint: UnwrapRef + readonly useElementHover: UnwrapRef + readonly useElementSize: UnwrapRef + readonly useElementVisibility: UnwrapRef + readonly useEventBus: UnwrapRef + readonly useEventListener: UnwrapRef + readonly useEventSource: UnwrapRef + readonly useEyeDropper: UnwrapRef + readonly useFavicon: UnwrapRef + readonly useFetch: UnwrapRef + readonly useFileDialog: UnwrapRef + readonly useFileSystemAccess: UnwrapRef + readonly useFocus: UnwrapRef + readonly useFocusWithin: UnwrapRef + readonly useFps: UnwrapRef + readonly useFullscreen: UnwrapRef + readonly useGamepad: UnwrapRef + readonly useGeolocation: UnwrapRef + readonly useIdle: UnwrapRef + readonly useImage: UnwrapRef + readonly useInfiniteScroll: UnwrapRef + readonly useIntersectionObserver: UnwrapRef + readonly useInterval: UnwrapRef + readonly useIntervalFn: UnwrapRef + readonly useKeyModifier: UnwrapRef + readonly useLastChanged: UnwrapRef + readonly useLocalStorage: UnwrapRef + readonly useMagicKeys: UnwrapRef + readonly useManualRefHistory: UnwrapRef + readonly useMediaControls: UnwrapRef + readonly useMediaQuery: UnwrapRef + readonly useMemoize: UnwrapRef + readonly useMemory: UnwrapRef + readonly useMounted: UnwrapRef + readonly useMouse: UnwrapRef + readonly useMouseInElement: UnwrapRef + readonly useMousePressed: UnwrapRef + readonly useMutationObserver: UnwrapRef + readonly useNavigatorLanguage: UnwrapRef + readonly useNetwork: UnwrapRef + readonly useNow: UnwrapRef + readonly useObjectUrl: UnwrapRef + readonly useOffsetPagination: UnwrapRef + readonly useOnline: UnwrapRef + readonly usePageLeave: UnwrapRef + readonly useParallax: UnwrapRef + readonly useParentElement: UnwrapRef + readonly usePerformanceObserver: UnwrapRef + readonly usePermission: UnwrapRef + readonly usePointer: UnwrapRef + readonly usePointerLock: UnwrapRef + readonly usePointerSwipe: UnwrapRef + readonly usePreferredColorScheme: UnwrapRef + readonly usePreferredContrast: UnwrapRef + readonly usePreferredDark: UnwrapRef + readonly usePreferredLanguages: UnwrapRef + readonly usePreferredReducedMotion: UnwrapRef + readonly usePrevious: UnwrapRef + readonly useRafFn: UnwrapRef + readonly useRefHistory: UnwrapRef + readonly useResizeObserver: UnwrapRef + readonly useScreenOrientation: UnwrapRef + readonly useScreenSafeArea: UnwrapRef + readonly useScriptTag: UnwrapRef + readonly useScroll: UnwrapRef + readonly useScrollLock: UnwrapRef + readonly useSessionStorage: UnwrapRef + readonly useShare: UnwrapRef + readonly useSlots: UnwrapRef + readonly useSorted: UnwrapRef + readonly useSpeechRecognition: UnwrapRef + readonly useSpeechSynthesis: UnwrapRef + readonly useStepper: UnwrapRef + readonly useStorage: UnwrapRef + readonly useStorageAsync: UnwrapRef + readonly useStyleTag: UnwrapRef + readonly useSupported: UnwrapRef + readonly useSwipe: UnwrapRef + readonly useTemplateRefsList: UnwrapRef + readonly useTextDirection: UnwrapRef + readonly useTextSelection: UnwrapRef + readonly useTextareaAutosize: UnwrapRef + readonly useThrottle: UnwrapRef + readonly useThrottleFn: UnwrapRef + readonly useThrottledRefHistory: UnwrapRef + readonly useTimeAgo: UnwrapRef + readonly useTimeout: UnwrapRef + readonly useTimeoutFn: UnwrapRef + readonly useTimeoutPoll: UnwrapRef + readonly useTimestamp: UnwrapRef + readonly useTitle: UnwrapRef + readonly useToNumber: UnwrapRef + readonly useToString: UnwrapRef + readonly useToggle: UnwrapRef + readonly useTransition: UnwrapRef + readonly useUrlSearchParams: UnwrapRef + readonly useUserMedia: UnwrapRef + readonly useVModel: UnwrapRef + readonly useVModels: UnwrapRef + readonly useVibrate: UnwrapRef + readonly useVirtualList: UnwrapRef + readonly useWakeLock: UnwrapRef + readonly useWebNotification: UnwrapRef + readonly useWebSocket: UnwrapRef + readonly useWebWorker: UnwrapRef + readonly useWebWorkerFn: UnwrapRef + readonly useWindowFocus: UnwrapRef + readonly useWindowScroll: UnwrapRef + readonly useWindowSize: UnwrapRef + readonly watch: UnwrapRef + readonly watchArray: UnwrapRef + readonly watchAtMost: UnwrapRef + readonly watchDebounced: UnwrapRef + readonly watchDeep: UnwrapRef + readonly watchEffect: UnwrapRef + readonly watchIgnorable: UnwrapRef + readonly watchImmediate: UnwrapRef + readonly watchOnce: UnwrapRef + readonly watchPausable: UnwrapRef + readonly watchPostEffect: UnwrapRef + readonly watchSyncEffect: UnwrapRef + readonly watchThrottled: UnwrapRef + readonly watchTriggerable: UnwrapRef + readonly watchWithFilter: UnwrapRef + readonly whenever: UnwrapRef + } +} diff --git a/src/types/components.d.ts b/src/types/components.d.ts new file mode 100644 index 0000000..5331f32 --- /dev/null +++ b/src/types/components.d.ts @@ -0,0 +1,81 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +import '@vue/runtime-core' + +export {} + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default'] + ElAlert: typeof import('element-plus/es')['ElAlert'] + ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] + ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] + ElCol: typeof import('element-plus/es')['ElCol'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElDivider: typeof import('element-plus/es')['ElDivider'] + ElDropdown: typeof import('element-plus/es')['ElDropdown'] + ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] + ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElIcon: typeof import('element-plus/es')['ElIcon'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElLink: typeof import('element-plus/es')['ElLink'] + ElMenu: typeof import('element-plus/es')['ElMenu'] + ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElPopover: typeof import('element-plus/es')['ElPopover'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElRow: typeof import('element-plus/es')['ElRow'] + ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTree: typeof import('element-plus/es')['ElTree'] + ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] + ElUpload: typeof import('element-plus/es')['ElUpload'] + GithubCorner: typeof import('./../components/GithubCorner/index.vue')['default'] + Hamburger: typeof import('./../components/Hamburger/index.vue')['default'] + IconSelect: typeof import('./../components/IconSelect/index.vue')['default'] + IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default'] + IEpCaretTop: typeof import('~icons/ep/caret-top')['default'] + IEpClose: typeof import('~icons/ep/close')['default'] + IEpCollection: typeof import('~icons/ep/collection')['default'] + IEpDelete: typeof import('~icons/ep/delete')['default'] + IEpDownload: typeof import('~icons/ep/download')['default'] + IEpEdit: typeof import('~icons/ep/edit')['default'] + IEpPlus: typeof import('~icons/ep/plus')['default'] + IEpPosition: typeof import('~icons/ep/position')['default'] + IEpRefresh: typeof import('~icons/ep/refresh')['default'] + IEpRefreshLeft: typeof import('~icons/ep/refresh-left')['default'] + IEpSearch: typeof import('~icons/ep/search')['default'] + IEpSetting: typeof import('~icons/ep/setting')['default'] + IEpTop: typeof import('~icons/ep/top')['default'] + IEpUploadFilled: typeof import('~icons/ep/upload-filled')['default'] + LangSelect: typeof import('./../components/LangSelect/index.vue')['default'] + MultiUpload: typeof import('./../components/Upload/MultiUpload.vue')['default'] + Pagination: typeof import('./../components/Pagination/index.vue')['default'] + RightPanel: typeof import('./../components/RightPanel/index.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + SingleUpload: typeof import('./../components/Upload/SingleUpload.vue')['default'] + SizeSelect: typeof import('./../components/SizeSelect/index.vue')['default'] + SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default'] + WangEditor: typeof import('./../components/WangEditor/index.vue')['default'] + } + export interface ComponentCustomProperties { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } +} diff --git a/src/types/env.d.ts b/src/types/env.d.ts new file mode 100644 index 0000000..bcddf3e --- /dev/null +++ b/src/types/env.d.ts @@ -0,0 +1,19 @@ +/// + +declare module '*.vue' { + import { DefineComponent } from 'vue'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any>; + export default component; +} + +// 环境变量 TypeScript的智能提示 +interface ImportMetaEnv { + VITE_APP_TITLE: string; + VITE_APP_PORT: string; + VITE_APP_BASE_API: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..ad7b72b --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,55 @@ +declare global { + /** + * 分页查询参数 + */ + interface PageQuery { + pageNum: number; + pageSize: number; + } + + /** + * 分页响应对象 + */ + interface PageResult { + /** + * 数据列表 + */ + list: T; + /** + * 数据总数 + */ + total: number; + } + + /** + * 弹窗属性 + */ + interface DialogOption { + /** + * 弹窗标题 + */ + title?: string; + /** + * 是否显示 + */ + visible: boolean; + } + /** + * 组件数据源 + */ + interface OptionType { + /** + * 值 + */ + value: number; + /** + * 文本 + */ + label: string; + /** + * 子列表 + */ + children?: OptionType[]; + } +} +export {}; diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts new file mode 100644 index 0000000..c081ede --- /dev/null +++ b/src/utils/i18n.ts @@ -0,0 +1,12 @@ +// translate router.meta.title, be used in breadcrumb sidebar tagsview +import i18n from '@/lang/index'; + +export function translateRouteTitleI18n(title: any) { + // 判断是否存在国际化配置,如果没有原生返回 + const hasKey = i18n.global.te('route.' + title); + if (hasKey) { + const translatedTitle = i18n.global.t('route.' + title); + return translatedTitle; + } + return title; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..0d62660 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,39 @@ +/** + * Check if an element has a class + * @param {HTMLElement} ele + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele: HTMLElement, cls: string) { + return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)")); +} + +/** + * Add class to element + * @param {HTMLElement} ele + * @param {string} cls + */ +export function addClass(ele: HTMLElement, cls: string) { + if (!hasClass(ele, cls)) ele.className += " " + cls; +} + +/** + * Remove class from element + * @param {HTMLElement} ele + * @param {string} cls + */ +export function removeClass(ele: HTMLElement, cls: string) { + if (hasClass(ele, cls)) { + const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)"); + ele.className = ele.className.replace(reg, " "); + } +} + +/** + * @param {string} path + * @returns {Boolean} + */ +export function isExternal(path: string) { + const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path); + return isExternal; +} diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..d8220de --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,85 @@ +import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; +import { useUserStoreHook } from '@/store/modules/user'; + +// 创建 axios 实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 50000, + headers: { 'Content-Type': 'application/json;charset=utf-8' } +}); + +// 请求拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + const userStore = useUserStoreHook(); + if (userStore.tokenIng) { + config.headers.Authorization = userStore.tokenIng; + } + if (userStore.userKey) { + config.headers["user-key"] = userStore.userKey; + } + if (userStore.userToken) { + config.headers["user-token"] = userStore.userToken; + } + return config; + }, + (error: any) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const { code, msg } = response.data; + if (code === 0) { + return response.data; + } + if (code === 7 || code === 300 || code === 301 || code === 302){ + ElMessageBox.confirm("身份令牌已失效!请重新登录!", "提示", { + confirmButtonText: "确定", + type: "warning", + }).then(() => { + localStorage.clear(); + window.location.href = "/"; + }); + return response.data; + } + // 响应数据为二进制流处理(Excel导出) + if (response.data instanceof ArrayBuffer) { + return response; + } + + ElMessage.error(msg || '系统出错'); + return Promise.reject(new Error(msg || 'Error')); + }, + (error: any) => { + if (error.response.data) { + const { code, msg } = error.response.data; + // token 过期,重新登录 + if (code === 'A0230') { + ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', { + confirmButtonText: '确定', + type: 'warning' + }).then(() => { + localStorage.clear(); + window.location.href = '/'; + }); + }else if(code === 7 || code === 300 || code === 301 || code === 302){ + ElMessageBox.confirm("身份令牌已失效!请重新登录!", "提示", { + confirmButtonText: "确定", + type: "warning", + }).then(() => { + localStorage.clear(); + window.location.href = "/"; + }); + } else { + ElMessage.error(msg || '系统出错'); + } + } + return Promise.reject(error.message); + } +); + +// 导出 axios 实例 +export default service; diff --git a/src/utils/scroll-to.ts b/src/utils/scroll-to.ts new file mode 100644 index 0000000..591e3ec --- /dev/null +++ b/src/utils/scroll-to.ts @@ -0,0 +1,69 @@ +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2; + if (t < 1) { + return (c / 2) * t * t + b; + } + t--; + return (-c / 2) * (t * (t - 2) - 1) + b; +}; + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +const requestAnimFrame = (function () { + return ( + window.requestAnimationFrame || + (window as any).webkitRequestAnimationFrame || + (window as any).mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + } + ); +})(); + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +const move = (amount: number) => { + document.documentElement.scrollTop = amount; + (document.body.parentNode as HTMLElement).scrollTop = amount; + document.body.scrollTop = amount; +}; + +const position = () => { + return ( + document.documentElement.scrollTop || + (document.body.parentNode as HTMLElement).scrollTop || + document.body.scrollTop + ); +}; + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export const scrollTo = (to: number, duration: number, callback?: any) => { + const start = position(); + const change = to - start; + const increment = 20; + let currentTime = 0; + duration = typeof duration === 'undefined' ? 500 : duration; + const animateScroll = function () { + // increment the time + currentTime += increment; + // find the value with the quadratic in-out easing function + const val = easeInOutQuad(currentTime, start, change, duration); + // move the document.body + move(val); + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll); + } else { + if (callback && typeof callback === 'function') { + // the animation is done so lets callback + callback(); + } + } + }; + animateScroll(); +}; diff --git a/src/views/dashboard/components/BarChart.vue b/src/views/dashboard/components/BarChart.vue new file mode 100644 index 0000000..225b713 --- /dev/null +++ b/src/views/dashboard/components/BarChart.vue @@ -0,0 +1,146 @@ + + + + diff --git a/src/views/dashboard/components/FunnelChart.vue b/src/views/dashboard/components/FunnelChart.vue new file mode 100644 index 0000000..3903d3a --- /dev/null +++ b/src/views/dashboard/components/FunnelChart.vue @@ -0,0 +1,106 @@ + + + + diff --git a/src/views/dashboard/components/PieChart.vue b/src/views/dashboard/components/PieChart.vue new file mode 100644 index 0000000..0f71ba5 --- /dev/null +++ b/src/views/dashboard/components/PieChart.vue @@ -0,0 +1,79 @@ + + + + diff --git a/src/views/dashboard/components/RadarChart.vue b/src/views/dashboard/components/RadarChart.vue new file mode 100644 index 0000000..f935036 --- /dev/null +++ b/src/views/dashboard/components/RadarChart.vue @@ -0,0 +1,100 @@ + + + + diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue new file mode 100644 index 0000000..7df0c77 --- /dev/null +++ b/src/views/dashboard/index.vue @@ -0,0 +1,250 @@ + + + + + + + diff --git a/src/views/demo/IconSelector.vue b/src/views/demo/IconSelector.vue new file mode 100644 index 0000000..8649850 --- /dev/null +++ b/src/views/demo/IconSelector.vue @@ -0,0 +1,10 @@ + + + + diff --git a/src/views/demo/apidoc.vue b/src/views/demo/apidoc.vue new file mode 100644 index 0000000..acd7fd7 --- /dev/null +++ b/src/views/demo/apidoc.vue @@ -0,0 +1,19 @@ + + + + diff --git a/src/views/demo/uploader.vue b/src/views/demo/uploader.vue new file mode 100644 index 0000000..6a22dff --- /dev/null +++ b/src/views/demo/uploader.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/views/demo/wangEditor.vue b/src/views/demo/wangEditor.vue new file mode 100644 index 0000000..8f4b5e8 --- /dev/null +++ b/src/views/demo/wangEditor.vue @@ -0,0 +1,11 @@ + + + + diff --git a/src/views/error-page/401.vue b/src/views/error-page/401.vue new file mode 100644 index 0000000..ae91a3e --- /dev/null +++ b/src/views/error-page/401.vue @@ -0,0 +1,114 @@ + + + + + + + + diff --git a/src/views/error-page/404.vue b/src/views/error-page/404.vue new file mode 100644 index 0000000..7902940 --- /dev/null +++ b/src/views/error-page/404.vue @@ -0,0 +1,280 @@ + + + + + + + + diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..fa4f064 --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/src/views/nested/level1/index.vue b/src/views/nested/level1/index.vue new file mode 100644 index 0000000..7daf19c --- /dev/null +++ b/src/views/nested/level1/index.vue @@ -0,0 +1,7 @@ + diff --git a/src/views/nested/level1/level2/index.vue b/src/views/nested/level1/level2/index.vue new file mode 100644 index 0000000..abcc3a7 --- /dev/null +++ b/src/views/nested/level1/level2/index.vue @@ -0,0 +1,7 @@ + diff --git a/src/views/nested/level1/level2/level3/index1.vue b/src/views/nested/level1/level2/level3/index1.vue new file mode 100644 index 0000000..888f58e --- /dev/null +++ b/src/views/nested/level1/level2/level3/index1.vue @@ -0,0 +1,5 @@ + diff --git a/src/views/nested/level1/level2/level3/index2.vue b/src/views/nested/level1/level2/level3/index2.vue new file mode 100644 index 0000000..a99c98e --- /dev/null +++ b/src/views/nested/level1/level2/level3/index2.vue @@ -0,0 +1,5 @@ + diff --git a/src/views/redirect/index.vue b/src/views/redirect/index.vue new file mode 100644 index 0000000..47cad96 --- /dev/null +++ b/src/views/redirect/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue new file mode 100644 index 0000000..87d6743 --- /dev/null +++ b/src/views/system/dept/index.vue @@ -0,0 +1,324 @@ + + diff --git a/src/views/system/dict/DictData.vue b/src/views/system/dict/DictData.vue new file mode 100644 index 0000000..0d6efbd --- /dev/null +++ b/src/views/system/dict/DictData.vue @@ -0,0 +1,315 @@ + + + + diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue new file mode 100644 index 0000000..403f2ba --- /dev/null +++ b/src/views/system/dict/index.vue @@ -0,0 +1,335 @@ + + + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..47bd88d --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,453 @@ + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..b3235e6 --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,424 @@ + + + diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..ec72d52 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,730 @@ + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b2fbbcf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "baseUrl": ".", + "allowJs": true, + "paths": { + "@/*": ["src/*"] + }, + "types": ["vite/client", "element-plus/global", "unplugin-icons/types/vue"], + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "allowSyntheticDefaultImports": true /* 允许默认导入 */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */ + }, + "include": ["src/**/*.ts", "src/**/*.vue", "src/types/**/*.d.ts"], + "exclude": ["node_modules", "dist", "**/*.js"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..0425950 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,156 @@ +import vue from "@vitejs/plugin-vue"; + +import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite"; + +import AutoImport from "unplugin-auto-import/vite"; +import Components from "unplugin-vue-components/vite"; +import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; + +import Icons from "unplugin-icons/vite"; +import IconsResolver from "unplugin-icons/resolver"; + +import { createSvgIconsPlugin } from "vite-plugin-svg-icons"; + +import UnoCSS from "unocss/vite"; + +import path from "path"; +const pathSrc = path.resolve(__dirname, "src"); + +export default defineConfig(({ mode }: ConfigEnv): UserConfig => { + const env = loadEnv(mode, process.cwd()); + return { + resolve: { + alias: { + "@": pathSrc, + }, + }, + css: { + // CSS 预处理器 + preprocessorOptions: { + //define global scss variable + scss: { + javascriptEnabled: true, + additionalData: ` + @use "@/styles/variables.scss" as *; + `, + }, + }, + }, + server: { + host: "0.0.0.0", + port: Number(env.VITE_APP_PORT), + open: true, // 运行是否自动打开浏览器 + proxy: { + // 反向代理解决跨域 + [env.VITE_APP_BASE_API]: { + target: env.VITE_APP_BASE_URL, // 线上接口地址 + // target: "http://vapi.youlai.tech", // 线上接口地址 + // target: 'http://localhost:8989', // 本地接口地址 , 后端工程仓库地址:https://gitee.com/youlaiorg/youlai-boot + changeOrigin: true, + rewrite: (path) => + path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 替换 /dev-api 为 target 接口地址 + }, + }, + }, + plugins: [ + vue(), + UnoCSS({ + /* options */ + }), + AutoImport({ + // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 + imports: ["vue", "@vueuse/core"], + eslintrc: { + enabled: false, // Default `false` + filepath: "./.eslintrc-auto-import.json", // Default `./.eslintrc-auto-import.json` + globalsPropValue: true, // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable') + }, + resolvers: [ + // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式) + ElementPlusResolver(), + // 自动导入图标组件 + IconsResolver({}), + ], + vueTemplate: true, // 是否在 vue 模板中自动导入 + dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 自动导入组件类型声明文件位置,默认根目录; false 关闭自动生成 + }), + + Components({ + resolvers: [ + // 自动注册图标组件 + IconsResolver({ + enabledCollections: ["ep"], //@iconify-json/ep 是 Element Plus 的图标库 + }), + // 自动导入 Element Plus 组件 + ElementPlusResolver(), + ], + dts: path.resolve(pathSrc, "types", "components.d.ts"), // 自动导入组件类型声明文件位置,默认根目录; false 关闭自动生成 + }), + + Icons({ + // 自动安装图标库 + autoInstall: true, + }), + + createSvgIconsPlugin({ + // 指定需要缓存的图标文件夹 + iconDirs: [path.resolve(pathSrc, "assets/icons")], + // 指定symbolId格式 + symbolId: "icon-[dir]-[name]", + }), + ], + + optimizeDeps: { + include: [ + "vue", + "vue-router", + "pinia", + "axios", + "element-plus/es/components/form/style/css", + "element-plus/es/components/form-item/style/css", + "element-plus/es/components/button/style/css", + "element-plus/es/components/input/style/css", + "element-plus/es/components/input-number/style/css", + "element-plus/es/components/switch/style/css", + "element-plus/es/components/upload/style/css", + "element-plus/es/components/menu/style/css", + "element-plus/es/components/col/style/css", + "element-plus/es/components/icon/style/css", + "element-plus/es/components/row/style/css", + "element-plus/es/components/tag/style/css", + "element-plus/es/components/dialog/style/css", + "element-plus/es/components/loading/style/css", + "element-plus/es/components/radio/style/css", + "element-plus/es/components/radio-group/style/css", + "element-plus/es/components/popover/style/css", + "element-plus/es/components/scrollbar/style/css", + "element-plus/es/components/tooltip/style/css", + "element-plus/es/components/dropdown/style/css", + "element-plus/es/components/dropdown-menu/style/css", + "element-plus/es/components/dropdown-item/style/css", + "element-plus/es/components/sub-menu/style/css", + "element-plus/es/components/menu-item/style/css", + "element-plus/es/components/divider/style/css", + "element-plus/es/components/card/style/css", + "element-plus/es/components/link/style/css", + "element-plus/es/components/breadcrumb/style/css", + "element-plus/es/components/breadcrumb-item/style/css", + "element-plus/es/components/table/style/css", + "element-plus/es/components/tree-select/style/css", + "element-plus/es/components/table-column/style/css", + "element-plus/es/components/select/style/css", + "element-plus/es/components/option/style/css", + "element-plus/es/components/pagination/style/css", + "element-plus/es/components/tree/style/css", + "element-plus/es/components/alert/style/css", + "@vueuse/core", + + "path-to-regexp", + "echarts", + "@wangeditor/editor", + "@wangeditor/editor-for-vue", + "vue-i18n", + ], + }, + }; +});