commit 77e9f1df5068c4de4f33204dfd87dcc68dc7a2cf Author: alex_q <914269@qq.com> Date: Fri Jun 12 12:31:35 2026 +0800 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..00ee2de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org +root = true + +# 表示所有文件适用 +[*] +charset = utf-8 # 设置文件字符集为 utf-8 +end_of_line = lf # 控制换行类型(lf | cr | crlf) +indent_style = space # 缩进风格(tab | space) +indent_size = 2 # 缩进大小 +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..a0be9af --- /dev/null +++ b/.env.development @@ -0,0 +1,11 @@ +# 应用端口 +VITE_APP_PORT = 3000 + +# 代理前缀 +VITE_APP_BASE_API = '/dev-api' + +# 接口地址 +VITE_APP_API_URL = http://localhost:8989 + +# 是否启用 Mock 服务 +VITE_MOCK_DEV_SERVER = false diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..74d45c1 --- /dev/null +++ b/.env.production @@ -0,0 +1,3 @@ +# 代理前缀 +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..adea0fa --- /dev/null +++ b/.eslintrc-auto-import.json @@ -0,0 +1,309 @@ +{ + "globals": { + "Component": true, + "ComponentPublicInstance": true, + "ComputedRef": true, + "EffectScope": true, + "ElMessage": true, + "ElMessageBox": true, + "ElNotification": true, + "InjectionKey": true, + "PropType": true, + "Ref": true, + "VNode": 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, + "createReusableTemplate": true, + "createSharedComposable": true, + "createTemplatePromise": 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, + "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, + "toValue": true, + "triggerRef": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unref": true, + "unrefElement": true, + "until": true, + "useActiveElement": true, + "useAnimate": true, + "useArrayDifference": true, + "useArrayEvery": true, + "useArrayFilter": true, + "useArrayFind": true, + "useArrayFindIndex": true, + "useArrayFindLast": true, + "useArrayIncludes": true, + "useArrayJoin": true, + "useArrayMap": true, + "useArrayReduce": true, + "useArraySome": true, + "useArrayUnique": true, + "useAsyncQueue": true, + "useAsyncState": true, + "useAttrs": true, + "useBase64": true, + "useBattery": true, + "useBluetooth": true, + "useBreakpoints": true, + "useBroadcastChannel": true, + "useBrowserLocation": true, + "useCached": true, + "useClipboard": true, + "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, + "useParentElement": true, + "usePerformanceObserver": true, + "usePermission": true, + "usePointer": true, + "usePointerLock": true, + "usePointerSwipe": true, + "usePreferredColorScheme": true, + "usePreferredContrast": true, + "usePreferredDark": true, + "usePreferredLanguages": true, + "usePreferredReducedMotion": true, + "usePrevious": true, + "useRafFn": true, + "useRefHistory": true, + "useResizeObserver": true, + "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, + "watchDeep": true, + "watchEffect": true, + "watchIgnorable": true, + "watchImmediate": true, + "watchOnce": true, + "watchPausable": true, + "watchPostEffect": true, + "watchSyncEffect": true, + "watchThrottled": true, + "watchTriggerable": true, + "watchWithFilter": true, + "whenever": true, + "ExtractDefaultPropTypes": true, + "ExtractPropTypes": true, + "ExtractPublicPropTypes": true, + "WritableComputedRef": true, + "acceptHMRUpdate": true, + "createPinia": true, + "defineStore": true, + "getActivePinia": true, + "injectLocal": true, + "mapActions": true, + "mapGetters": true, + "mapState": true, + "mapStores": true, + "mapWritableState": true, + "onBeforeRouteLeave": true, + "onBeforeRouteUpdate": true, + "provideLocal": true, + "setActivePinia": true, + "setMapStoreSuffix": true, + "storeToRefs": true, + "useClipboardItems": true, + "useI18n": true, + "useLink": true, + "useRoute": true, + "useRouter": true + } +} diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..8ae169a --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,88 @@ +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true, + }, + parser: "vue-eslint-parser", + extends: [ + // https://eslint.vuejs.org/user-guide/#usage + "plugin:vue/vue3-recommended", + "./.eslintrc-auto-import.json", + "prettier", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + ], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: "@typescript-eslint/parser", + project: "./tsconfig.*?.json", + createDefaultProgram: false, + extraFileExtensions: [".vue"], + }, + plugins: ["vue", "@typescript-eslint"], + rules: { + // https://eslint.vuejs.org/rules/#priority-a-essential-error-prevention + "vue/multi-word-component-names": "off", + "vue/no-v-model-argument": "off", + "vue/script-setup-uses-vars": "error", + "vue/no-reserved-component-names": "off", + "vue/custom-event-name-casing": "off", + "vue/attributes-order": "off", + "vue/one-component-per-file": "off", + "vue/html-closing-bracket-newline": "off", + "vue/max-attributes-per-line": "off", + "vue/multiline-html-element-content-newline": "off", + "vue/singleline-html-element-content-newline": "off", + "vue/attribute-hyphenation": "off", + "vue/require-default-prop": "off", + "vue/require-explicit-emits": "off", + "vue/html-self-closing": [ + "error", + { + html: { + void: "always", + normal: "never", + component: "always", + }, + svg: "always", + math: "always", + }, + ], + + "@typescript-eslint/no-empty-function": "off", // 关闭空方法检查 + "@typescript-eslint/no-explicit-any": "off", // 关闭any类型的警告 + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-unused-vars": "off", + + "prettier/prettier": [ + "error", + { + useTabs: false, // 不使用制表符 + }, + ], + }, + // eslint不能对html文件生效 + overrides: [ + { + files: ["*.html"], + processor: "vue/.vue", + }, + ], + // https://eslint.org/docs/latest/use/configure/language-options#specifying-globals + globals: { + OptionType: "readonly", + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fee6999 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +.history + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +pnpm-lock.yaml +stats.html diff --git a/.hintrc b/.hintrc new file mode 100644 index 0000000..8cc5c90 --- /dev/null +++ b/.hintrc @@ -0,0 +1,15 @@ +{ + "extends": [ + "development" + ], + "hints": { + "compat-api/css": [ + "default", + { + "ignore": [ + "text-size-adjust" + ] + } + ] + } +} \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..fd2bf70 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no-install commitlint --edit $1 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..b18d041 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npm run lint:lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f3e9850 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +dist +node_modules +public +.husky +.vscode +.idea +*.sh +*.md + +src/assets +stats.html diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..7ce60da --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,46 @@ +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: "ignore", + // 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false + insertPragma: false, + // 在 JSX 中使用单引号替代双引号,默认false + jsxSingleQuote: false, + // 每行最多字符数量,超出换行(默认80) + printWidth: 120, + // 超出打印宽度 (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 文件中是否缩进 ", + "" + ], + "description": "Vue3.0" + } +} diff --git a/.vscode/vue3.2.code-snippets b/.vscode/vue3.2.code-snippets new file mode 100644 index 0000000..a083940 --- /dev/null +++ b/.vscode/vue3.2.code-snippets @@ -0,0 +1,17 @@ +{ + "Vue3.2+快速生成模板": { + "scope": "vue", + "prefix": "Vue3.2+", + "body": [ + "", + "", + "", + "", + "", + "" + ], + "description": "Vue3.2+" + } +} diff --git a/.vscode/vue3.3.code-snippets b/.vscode/vue3.3.code-snippets new file mode 100644 index 0000000..705e04f --- /dev/null +++ b/.vscode/vue3.3.code-snippets @@ -0,0 +1,21 @@ +{ + "Vue3.3+defineOptions快速生成模板": { + "scope": "vue", + "prefix": "Vue3.3+", + "body": [ + "", + "", + "", + "", + "", + "" + ], + "description": "Vue3.3+defineOptions快速生成模板" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..98d324d --- /dev/null +++ b/README.md @@ -0,0 +1,191 @@ + +
+ vue3-element-admin +

vue3-element-admin

+ + + + + + + + +
+ +![](https://foruda.gitee.com/images/1708618984641188532/a7cca095_716974.png "rainbow.png") + + +
+ 🔍 在线预览 | 📖 阅读文档 | 🌐English +
+ + + +## 项目简介 + +[vue3-element-admin](https://gitee.com/youlaiorg/vue3-element-admin) 是基于 Vue3 + Vite5+ TypeScript5 + Element-Plus + Pinia 等主流技术栈构建的免费开源的中后台管理的前端模板(配套[Java 后端源码](https://gitee.com/youlaiorg/youlai-boot))。 + + +## 项目特色 + +- **简洁易用**:基于 [vue-element-admin](https://gitee.com/panjiachen/vue-element-admin) 升级的 Vue3 版本,无过渡封装 ,易上手。 + +- **数据交互**:同时支持本地 `Mock` 和线上接口,配套 [Java 后端源码](https://gitee.com/youlaiorg/youlai-boot)和[在线接口文档](https://www.apifox.cn/apidoc/shared-195e783f-4d85-4235-a038-eec696de4ea5)。 + +- **权限管理**:用户、角色、菜单、字典、部门等完善的权限系统功能。 + +- **基础设施**:动态路由、按钮权限、国际化、代码规范、Git 提交规范、常用组件封装。 + +- **持续更新**:项目持续开源更新,实时更新工具和依赖。 + + + +## 项目预览 + +![明亮模式](https://foruda.gitee.com/images/1709651876583793739/0ba1ee1c_716974.png) + +![暗黑模式](https://foruda.gitee.com/images/1709651875494206224/2a2b0b53_716974.png) + +![接口文档](https://foruda.gitee.com/images/1687755822857820115/96054330_716974.png) + +## 项目地址 + +| 项目 | Gitee | Github | +| ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 前端 | [vue3-element-admin](https://gitee.com/youlaiorg/vue3-element-admin) | [vue3-element-admin](https://github.com/youlaitech/vue3-element-admin) | [vue3-element-admin](https://gitcode.net/youlai/vue3-element-admin) | +| 精简版 | [vue3-element-admin-thin](https://gitee.com/cshaptx4869/vue3-element-admin-thin) | [vue3-element-admin-thin](https://github.com/youlaitech/vue3-element-admin-thin) | +| 后端 | [youlai-boot](https://gitee.com/youlaiorg/youlai-boot) | [youlai-boot](https://github.com/haoxianrui/youlai-boot.git) | + +## 环境准备 + +| 环境 | 名称版本 | 下载地址 | +| -------------------- | :----------------------------------------------------------- | ------------------------------------------------------------ | +| **开发工具** | VSCode | [下载](https://code.visualstudio.com/Download) | +| **运行环境** | Node ≥18 (其中 20.6.0 版本不可用) | [下载](http://nodejs.cn/download) | + + +## 项目启动 + +```bash +# 克隆代码 +git clone https://gitee.com/youlaiorg/vue3-element-admin.git + +# 切换目录 +cd vue3-element-admin + +# 安装 pnpm +npm install pnpm -g + +# 设置镜像源(可忽略) +pnpm config set registry https://registry.npmmirror.com + +# 安装依赖 +pnpm install + +# 启动运行 +pnpm run dev +``` + + + +## 项目部署 + +```bash +# 项目打包 +pnpm run build + +# 上传文件至远程服务器 +将本地打包生成的 dist 目录下的所有文件拷贝至服务器的 /usr/share/nginx/html 目录。 + +# nginx.cofig 配置 +server { + listen 80; + server_name localhost; + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + # 反向代理配置 + location /prod-api/ { + # vapi.youlai.tech 替换后端API地址,注意保留后面的斜杠 / + proxy_pass http://vapi.youlai.tech/; + } +} +``` + +## 本地Mock + +项目同时支持在线和本地 Mock 接口,默认使用线上接口,如需替换为 Mock 接口,修改文件 `.env.development` 的 `VITE_MOCK_DEV_SERVER` 为 `true` **即可**。 + +## 后端接口 + +> 如果您具备Java开发基础,按照以下步骤将在线接口转为本地后端接口,创建企业级前后端分离开发环境,助您走向全栈之路。 + +1. 获取基于 `Java` 和 `SpringBoot` 开发的后端 [youlai-boot](https://gitee.com/youlaiorg/youlai-boot.git) 源码。 +2. 根据后端工程的说明文档 [README.md](https://gitee.com/youlaiorg/youlai-boot#%E9%A1%B9%E7%9B%AE%E8%BF%90%E8%A1%8C) 完成本地启动。 +3. 修改 `.env.development` 文件中的 `VITE_APP_API_URL` 的值,将其从 http://vapi.youlai.tech 更改为 http://localhost:8989。 + + +## 注意事项 + +- **自动导入插件自动生成默认关闭** + + 模板项目的组件类型声明已自动生成。如果添加和使用新的组件,请按照图示方法开启自动生成。在自动生成完成后,记得将其设置为 `false`,避免重复执行引发冲突。 + + ![](https://foruda.gitee.com/images/1687755823137387608/412ea803_716974.png) + +- **项目启动浏览器访问空白** + + 请升级浏览器尝试,低版本浏览器内核可能不支持某些新的 JavaScript 语法,比如可选链操作符 `?.`。 + +- **项目同步仓库更新升级** + + 项目同步仓库更新升级之后,建议 `pnpm install` 安装更新依赖之后启动 。 + +- **项目组件、函数和引用爆红** + + 重启 VSCode 尝试 + +- **其他问题** + + 如果有其他问题或者建议,建议 [ISSUE](https://gitee.com/youlaiorg/vue3-element-admin/issues/new) + + + +## 项目文档 + +- [基于 Vue3 + Vite + TypeScript + Element-Plus 从0到1搭建后台管理系统](https://blog.csdn.net/u013737132/article/details/130191394) + +- [ESLint+Prettier+Stylelint+EditorConfig 约束和统一前端代码规范](https://blog.csdn.net/u013737132/article/details/130190788) +- [Husky + Lint-staged + Commitlint + Commitizen + cz-git 配置 Git 提交规范](https://blog.csdn.net/u013737132/article/details/130191363) + + +## 提交规范 + +执行 `pnpm run commit` 唤起 git commit 交互,根据提示完成信息的输入和选择。 + +![](https://foruda.gitee.com/images/1687755823165218215/c1705416_716974.png) + + +## 项目统计 + +![Alt](https://repobeats.axiom.co/api/embed/aa7cca3d6fa9c308fc659fa6e09af9a1910506c3.svg "Repobeats analytics image") + + +Thanks to all the contributors! + +[![contributors](https://contrib.rocks/image?repo=youlaitech/vue3-element-admin)](https://github.com/youlaitech/vue3-element-admin/graphs/contributors) + + +## 交流群🚀 + +> **关注「有来技术」公众号,获取交流群二维码。** +> +> 如果交流群的二维码过期,请加微信(haoxianrui)并备注「前端」、「后端」或「全栈」以获取最新二维码。 +> +> 为确保交流群质量,防止营销广告人群混入,我们采取了此措施。望各位理解! + +| 公众号 | 交流群 | +|:----:|:----:| +| ![有来技术公众号二维码](https://foruda.gitee.com/images/1687689212187063809/3c69eaee_716974.png) | ![交流群二维码](https://foruda.gitee.com/images/1687689212139273561/6a65ef69_716974.png) | + diff --git a/commitlint.config.cjs b/commitlint.config.cjs new file mode 100644 index 0000000..f30d285 --- /dev/null +++ b/commitlint.config.cjs @@ -0,0 +1,94 @@ +module.exports = { + // 继承的规则 + extends: ["@commitlint/config-conventional"], + // 自定义规则 + rules: { + // @see https://commitlint.js.org/#/reference-rules + + // 提交类型枚举,git提交type必须是以下类型 + "type-enum": [ + 2, + "always", + [ + "feat", // 新增功能 + "fix", // 修复缺陷 + "docs", // 文档变更 + "style", // 代码格式(不影响功能,例如空格、分号等格式修正) + "refactor", // 代码重构(不包括 bug 修复、功能新增) + "perf", // 性能优化 + "test", // 添加疏漏测试或已有测试改动 + "build", // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等) + "ci", // 修改 CI 配置、脚本 + "revert", // 回滚 commit + "chore", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例) + ], + ], + "subject-case": [0], // subject大小写不做校验 + }, + + prompt: { + messages: { + type: "选择你要提交的类型 :", + scope: "选择一个提交范围(可选):", + customScope: "请输入自定义的提交范围 :", + subject: "填写简短精炼的变更描述 :\n", + body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n', + breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n', + footerPrefixesSelect: "选择关联issue前缀(可选):", + customFooterPrefix: "输入自定义issue前缀 :", + footer: "列举关联issue (可选) 例如: #31, #I3244 :\n", + generatingByAI: "正在通过 AI 生成你的提交简短描述...", + generatedSelectByAI: "选择一个 AI 生成的简短描述:", + confirmCommit: "是否提交或修改commit ?", + }, + // prettier-ignore + types: [ + { value: "feat", name: "特性: ✨ 新增功能", emoji: ":sparkles:" }, + { value: "fix", name: "修复: 🐛 修复缺陷", emoji: ":bug:" }, + { value: "docs", name: "文档: 📝 文档变更(更新README文件,或者注释)", emoji: ":memo:" }, + { value: "style", name: "格式: 🌈 代码格式(空格、格式化、缺失的分号等)", emoji: ":lipstick:" }, + { value: "refactor", name: "重构: 🔄 代码重构(不修复错误也不添加特性的代码更改)", emoji: ":recycle:" }, + { value: "perf", name: "性能: 🚀 性能优化", emoji: ":zap:" }, + { value: "test", name: "测试: 🧪 添加疏漏测试或已有测试改动", emoji: ":white_check_mark:"}, + { value: "build", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)", emoji: ":package:"}, + { value: "ci", name: "集成: ⚙️ 修改 CI 配置、脚本", emoji: ":ferris_wheel:"}, + { value: "revert", name: "回退: ↩️ 回滚 commit",emoji: ":rewind:"}, + { value: "chore", name: "其他: 🛠️ 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: ":hammer:"}, + { value: "wip", name: "开发中: 🚧 开发阶段临时提交", emoji: ":construction:"}, + ], + useEmoji: true, + emojiAlign: "center", + useAI: false, + aiNumber: 1, + themeColorCode: "", + scopes: [], + allowCustomScopes: true, + allowEmptyScopes: true, + customScopesAlign: "bottom", + customScopesAlias: "custom", + emptyScopesAlias: "empty", + upperCaseSubject: false, + markBreakingChangeMode: false, + allowBreakingChanges: ["feat", "fix"], + breaklineNumber: 100, + breaklineChar: "|", + skipQuestions: [], + issuePrefixes: [ + { value: "closed", name: "closed: ISSUES has been processed" }, + ], + customIssuePrefixAlign: "top", + emptyIssuePrefixAlias: "skip", + customIssuePrefixAlias: "custom", + allowCustomIssuePrefix: true, + allowEmptyIssuePrefix: true, + confirmColorize: true, + maxHeaderLength: Infinity, + maxSubjectLength: Infinity, + minSubjectLength: 0, + scopeOverrides: undefined, + defaultBody: "", + defaultIssues: "", + defaultScope: "", + defaultSubject: "", + }, +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..418cd2d --- /dev/null +++ b/index.html @@ -0,0 +1,137 @@ + + + + + + + + + 唱作网 + + + + + + +
+ +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + diff --git a/mock/auth.mock.ts b/mock/auth.mock.ts new file mode 100644 index 0000000..90f08f7 --- /dev/null +++ b/mock/auth.mock.ts @@ -0,0 +1,43 @@ +import { defineMock } from "./base"; + +export default defineMock([ + { + url: "auth/captcha", + method: ["GET"], + body: { + code: "00000", + data: { + captchaKey: "534b8ef2b0a24121bec76391ddd159f9", + captchaBase64: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAkCAIAAADNSmkJAAAFKUlEQVR4Xu2ZXUwcVRiGV70wMWo08V5NvPXCrDbFaGpMaZW2hqQxaoiJTRsaMBCNSYtpa2JTKiFSelFa+Q/QZcMWqEhBlh+htbEpZhMrBQrlJ0hBywLLyrJ0WZbje3bqOvPNLHPWrDvdOE9ONmfe78zkzMs335wzWJhJQrBQweS/wTQ6QWgYHdoIOcecOe05O+t2WkutO+p2ZF3Ksg/YV9ZW6FATYajR3nveg60H9327r3O8c35lHgp+r05dPdJzBL73TPSQ8SaCKIxGLsPlop+K0JHrEkPuoT31e5qGmmjARACF0agYyGVNlyVm/pzZXrN9fHGcBkz0UBid+31u93i3XFFT80vN8cvHqWqih8Lo1NpUqS5vwh3vnd223VQ10UNh9NbyrcFQUK6oCawHUipSqGqiB83oBf+CXFGDMp1mS6OqiR4Ko7FexkpOrqhpHGw82nOUqiZ6KIzGrkRuorW0dJMmOy+hOCfYGzb2RBFv6HRO0gEJw/U7y+pgL1bwmTxexN6sZ31TdEwEhdG+gA+7EqyXpUO1uZH20cWL8hMTRt1N9tBXzCJrOIRoCPJpSO2RAp4HmtCdIfZ+2JWgEBN9LbR28seTGU0Zue1tMLp+YIAMSADzfvbkKX4/eb28j4YODiGin3heqmIlLja5hAUCu+nmGY3JWKvpMAlqNGgebsauBOvlqSX+JEx7p7EbTLen53XlzfmWUioqXikrc68Y8N2juJ/fyVsNChGHEE//rBANYWaZz+TRQqpLaBgNsPfDrgSpbS21YtV87IdjrlkX9JZbt5DOma2t9ITo5F+5glN22WwL/n+yDv00mw06orKxOqQ5+J04hhViwzAXETIcJDVm8uxZqktoGx2Nj9t43Wgaul/ERQiGQvtbWnDWgZYW9CXlQFjZ/7ciyHNn+Z2MexTimIeLz59TiIln0M1e+IbPpOAaDUnEYPTi6iqKxpbycs/qKo1tCslfKcffPn9enuMiPPY1vxO/ckeFQ4h46cdGqUWoidE/y54q5tPY5WDrGzQqIXot4BgchEE57e00IMCw2/1qZSVO/7SjA78o9INzcxsbrL+fnTnDDh9mmZn8F30oG1Hm+nABv5mQMopDS/h1HxtqTzWbABMe9sxpPoe9zezeOo1GELqWhPS8t46M0IAYHbdvR1aHbaOjbjfLz2eFhez6dba4yAfgF30o0BFVE8+Mjh/wFxPI+I5mAEHU6Ls+38vhTFwOBGhMDF8gkFpbC5ffsdv/uBs6dIj19dExEtARVXv9YNbop8NFY3aZ6gRRo+tu3IBHnzmdNCBMXldXJKPfL74WzWUJRE+coDUknqsOdZXQbAJYwluVTbOZI3Qt8GFzMwxyjo3RgBiN4fr+elXVpZGRLWXl6PdOTtJBSlBDUK/lnIrjOlrtqWYTQDJaF6FrTXu9sOa1ysrVoM5HVE1GFxZQcyJ/p+xzv6K/rbr6N6+XDpUBl0tKFIrbz78qWB6YnWFMCBld4XLBms+7df75ook/GNzb0GCV7U1Qfz9p64TyQWNjYD3qe9rj4SMJtQP3MyjSDPzWIRHPjH7X4YAvfXoPuyZf9Pbi3PcuXIh4mp3NllYC6XY79C+jl2o8PBipxjnBttn4MgMNnWgfcRJGPI2OL8hTj3LloIlmRicvBhiNykvecpqoa3RSY4DRcLAwyicuOepVR1JjgNFYHWONHL04czTX0UmNAUYD7Pr+xc4wqTHGaBb2OtZvHUmNYUazcA2J6etdUmOk0f8rTKMTxF91RG0D1SwYGwAAAABJRU5ErkJggg==", + }, + msg: "一切ok", + }, + }, + + { + url: "auth/login", + method: ["POST"], + body: { + code: "00000", + data: { + accessToken: + "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImp0aSI6IjE2YWJkNTlkOTAxNzQwZDliYmI3ZjczODBhZDkyNzNhIiwidXNlcklkIjoyLCJ1c2VybmFtZSI6ImFkbWluIiwiZGVwdElkIjoxLCJkYXRhU2NvcGUiOjEsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNjkxMTAzMzgyfQ.P4cuIfmPepl3HuguhMS7NXn5a7IUPpsLbmtA_rHOhHk", + tokenType: "Bearer", + refreshToken: null, + expires: null, + }, + msg: "一切ok", + }, + }, + + { + url: "auth/logout", + method: ["DELETE"], + body: { + code: "00000", + data: {}, + msg: "string", + }, + }, +]); diff --git a/mock/base.ts b/mock/base.ts new file mode 100644 index 0000000..438e1c1 --- /dev/null +++ b/mock/base.ts @@ -0,0 +1,10 @@ +import path from "path"; +import { createDefineMock } from "vite-plugin-mock-dev-server"; + +export const defineMock = createDefineMock((mock) => { + // 拼接url + mock.url = path.join( + import.meta.env.VITE_APP_BASE_API + "/api/v1/", + mock.url + ); +}); diff --git a/mock/menu.mock.ts b/mock/menu.mock.ts new file mode 100644 index 0000000..e8a85a2 --- /dev/null +++ b/mock/menu.mock.ts @@ -0,0 +1,120 @@ +import { defineMock } from "./base"; + +export default defineMock([ + { + url: "menus/routes", + method: ["GET"], + body: { + code: "00000", + data: [ + { + path: "/doc", + component: "Layout", + redirect: "https://juejin.cn/post/7228990409909108793", + name: "/doc", + meta: { + title: "平台文档", + icon: "document", + hidden: false, + alwaysShow: false, + params: null, + }, + children: [ + { + path: "internal-doc", + component: "demo/internal-doc", + name: "InternalDoc", + meta: { + title: "平台文档(内嵌)", + icon: "document", + hidden: false, + alwaysShow: false, + params: null, + }, + }, + { + path: "https://juejin.cn/post/7228990409909108793", + name: "Https://juejin.cn/post/7228990409909108793", + meta: { + title: "平台文档(外链)", + icon: "el-icon-Link", + hidden: false, + alwaysShow: false, + params: null, + }, + }, + ], + }, + { + path: "/multi-level", + component: "Layout", + name: "/multiLevel", + meta: { + title: "多级菜单", + icon: "cascader", + hidden: false, + alwaysShow: true, + params: null, + }, + children: [ + { + path: "multi-level1", + component: "demo/multi-level/level1", + name: "MultiLevel1", + meta: { + title: "菜单一级", + icon: "", + hidden: false, + alwaysShow: true, + params: null, + }, + children: [ + { + path: "multi-level2", + component: "demo/multi-level/children/level2", + name: "MultiLevel2", + meta: { + title: "菜单二级", + icon: "", + hidden: false, + alwaysShow: false, + params: null, + }, + children: [ + { + path: "multi-level3-1", + component: "demo/multi-level/children/children/level3-1", + name: "MultiLevel31", + meta: { + title: "菜单三级-1", + icon: "", + hidden: false, + keepAlive: true, + alwaysShow: false, + params: null, + }, + }, + { + path: "multi-level3-2", + component: "demo/multi-level/children/children/level3-2", + name: "MultiLevel32", + meta: { + title: "菜单三级-2", + icon: "", + hidden: false, + keepAlive: true, + alwaysShow: false, + params: null, + }, + }, + ], + }, + ], + }, + ], + }, + ], + msg: "一切ok", + }, + }, +]); diff --git a/mock/user.mock.ts b/mock/user.mock.ts new file mode 100644 index 0000000..639a183 --- /dev/null +++ b/mock/user.mock.ts @@ -0,0 +1,44 @@ +import { defineMock } from "./base"; + +export default defineMock([ + { + url: "users/me", + method: ["GET"], + body: { + code: "00000", + data: { + userId: 2, + nickname: "系统管理员", + username: "admin", + avatar: + "https://foruda.gitee.com/images/1723603502796844527/03cdca2a_716974.gif?imageView2/1/w/80/h/80", + roles: ["ROOT"], + perms: [ + "sys:menu:delete", + "sys:dept:edit", + "sys:dict_type:add", + "sys:dict:edit", + "sys:dict:delete", + "sys:dict_type:edit", + "sys:menu:add", + "sys:user:add", + "sys:role:edit", + "sys:dept:delete", + "sys:user:edit", + "sys:user:delete", + "sys:user:password:reset", + "sys:dept:add", + "sys:role:delete", + "sys:dict_type:delete", + "sys:menu:edit", + "sys:dict:add", + "sys:role:add", + "sys:user:query", + "sys:user:export", + "sys:user:import", + ], + }, + msg: "一切ok", + }, + }, +]); diff --git a/package.json b/package.json new file mode 100644 index 0000000..14131e5 --- /dev/null +++ b/package.json @@ -0,0 +1,118 @@ +{ + "name": "ichangzuo", + "version": "1.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit & vite build", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --noEmit", + "lint:eslint": "eslint --fix --ext .ts,.js,.vue ./src ", + "lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"", + "lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix", + "lint:lint-staged": "lint-staged", + "preinstall": "npx only-allow pnpm", + "prepare": "husky", + "commit": "git-cz" + }, + "config": { + "commitizen": { + "path": "node_modules/cz-git" + } + }, + "lint-staged": { + "*.{js,ts}": [ + "eslint --fix", + "prettier --write" + ], + "*.{cjs,json}": [ + "prettier --write" + ], + "*.{vue,html}": [ + "eslint --fix", + "prettier --write", + "stylelint --fix" + ], + "*.{scss,css}": [ + "stylelint --fix", + "prettier --write" + ], + "*.md": [ + "prettier --write" + ] + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@vueuse/core": "^12.0.0", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "5.1.10", + "animate.css": "^4.1.1", + "axios": "^1.7.8", + "color": "^4.2.3", + "element-plus": "^2.9.0", + "nprogress": "^0.2.0", + "path-browserify": "^1.0.1", + "path-to-regexp": "^8.2.0", + "pinia": "^2.2.8", + "qs": "^6.13.1", + "vue": "^3.5.13", + "vue-i18n": "10.0.5", + "vue-qrcode": "^2.2.2", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@commitlint/cli": "^19.6.0", + "@commitlint/config-conventional": "^19.6.0", + "@iconify-json/ep": "^1.2.1", + "@types/color": "^4.2.0", + "@types/node": "^22.10.1", + "@types/nprogress": "^0.2.3", + "@types/path-browserify": "^1.0.3", + "@types/qs": "^6.9.17", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "@typescript-eslint/parser": "^8.17.0", + "@vitejs/plugin-vue": "^5.2.1", + "@vitejs/plugin-vue-jsx": "^4.1.1", + "autoprefixer": "^10.4.20", + "commitizen": "^4.3.1", + "cz-git": "^1.11.0", + "eslint": "^9.16.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-vue": "^9.32.0", + "fast-glob": "^3.3.2", + "husky": "^9.1.7", + "lint-staged": "^15.2.10", + "postcss": "^8.4.49", + "postcss-html": "^1.7.0", + "postcss-scss": "^4.0.9", + "prettier": "^3.4.1", + "sass": "^1.82.0", + "stylelint": "^16.11.0", + "stylelint-config-html": "^1.1.0", + "stylelint-config-recess-order": "^5.1.1", + "stylelint-config-recommended-scss": "^14.1.0", + "stylelint-config-recommended-vue": "^1.5.0", + "stylelint-config-standard": "^36.0.1", + "terser": "^5.36.0", + "typescript": "^5.7.2", + "unocss": "^0.65.0", + "unplugin-auto-import": "^0.18.6", + "unplugin-icons": "^0.21.0", + "unplugin-vue-components": "^0.27.5", + "vite": "^6.0.2", + "vite-plugin-mock-dev-server": "^1.8.0", + "vite-plugin-svg-icons": "^2.0.1", + "vite-plugin-vue-devtools": "^7.6.7", + "vue-tsc": "^2.1.10" + }, + "repository": "", + "author": "", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..db1eae6 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/html/coin.html b/public/html/coin.html new file mode 100644 index 0000000..fd3c34b --- /dev/null +++ b/public/html/coin.html @@ -0,0 +1,66 @@ + + +
+
唱作音符
+ +
+
+
+ 唱作音符是唱作网的操作度量工具,在使用唱作魔方软件时会用到唱作音符。唱作音符通常叫作“音符”。 +
+
+ +
+
音符使用:
+
+ 在唱作魔方软件中:
+ (1)保存音乐 (-1)
+ (2)保存音乐时使用扩展音色库 (-80)
+ (3)保存音乐时使用收费风格
+ (4)其它 +
+
+ +
+
用户获取:
+
+ (1)在注册用户时唱作网会赠予一定的音符
+ (2)充值兑换
+
+
+ +
+
音符充值:
+
+ 进入账户管理页,选择 "唱作音符" 下的 "充值提现"
+ 当前兑换比例:¥1元 = 100音符 +
+
+ +
+
音符提现:
+
+ 进入账户管理页,选择 "唱作音符" 下的 "充值提现"
+ 最低提现金额为100元(即10000音符);唱作网将收取按提现金额6%的音符作为平台管理费,提现后24小时内到账 +
+
+ +
+
查看明细:
+
+ 进入账户管理页,选择 "唱作音符" 下的 "音符明细" 查看
+
+
+ +
+
+ 注:音符获取细则和使用细则等请以唱作网具体规定为准

+
+
+ +
+ +
+ + + diff --git a/public/html/contact.html b/public/html/contact.html new file mode 100644 index 0000000..0593d4d --- /dev/null +++ b/public/html/contact.html @@ -0,0 +1,23 @@ + + +
+
联系我们
+ +
+
+
+
+
+ 邮箱:music@ichangzuo.com +
+
+ QQ群:271573952 +
+
+ +
+ +
+ + + diff --git a/public/html/help.css b/public/html/help.css new file mode 100644 index 0000000..2babfca --- /dev/null +++ b/public/html/help.css @@ -0,0 +1,15 @@ +@charset "utf-8"; + +/*帮助内容*/ +.helpContent_border{width:100%; margin:0 0 30px 0px;} +.helpContent_border .helpContent_title{width:100%; padding: 20px 0 10px 0; text-align:center; font-size:20px; font-weight:bold;} +.helpContent_border .content{width:100%; padding:25px 0 0 0;} +.helpContent_border .content .title{font-size:16px; font-weight:bold;padding-left: 10px;} +.helpContent_border .content .msg{padding:10px 0 0 20px; font-size:12px; line-height:24px;} +.helpContent_border .content .img_border{width:100%; padding:20px 0 20px 0; text-align:center;} +.helpContent_border .content .img_border img{max-width:850px;} +table, th, td { + border: 1px solid black; + border-collapse: collapse; + text-align: center; +} diff --git a/public/html/privacy.html b/public/html/privacy.html new file mode 100644 index 0000000..1fcee3f --- /dev/null +++ b/public/html/privacy.html @@ -0,0 +1,156 @@ + + +
+
唱作网隐私政策
+ +
+
+
+
+ +
+
一、 我们如何收集和使用您的个人信息
+
+

1、完成注册

+ 注册账号时,我们会要求您提供相应的个人信息,包括您的账号、密码、邮箱、手机号码等。
+

2、使用产品

+ (1)我们会收集您在使用我们的产品和服务时设备的相关信息,例如设备类型、操作系统类型及版本、浏览器类型和版本、屏幕分辨率、IP地址等,以及因产品而异的其他技术信息。
+ (2)在您使用我们的产品或服务,以及与我们互动时由您直接提交给我们的信息,包括您输入的信息或您为了获得客服支持而提供的问题或信息等。
+ (3)当您使用我们的产品和服务时,我们会自动收集并存储必要的日志信息,如服务的每次访问时间、访问的页面路径、客户端 IP地址、事件信息(如错误、崩溃、重启、升级)等。 +

3、实名认证

+ 当您使用我们的部分产品与/或服务时,根据相关法律法规要求,需要您进行实名认证。实名认证需要上传实名认证信息。不同的实名认证主体类型会要求不同实名认证信息,这些信息包括:姓名、证件号码、公司名称、公司统一信用代码、学生证信息、身份证信息等。
+ 上述信息将仅在您使用我们的产品与/或服务期间持续授权我们保存使用。在您主动注销账号时,我们将根据适用法律法规的要求尽快使其匿名或删除您的个人信息。 +

4、充值提现

+ (1)您可以通过微信支付、支付宝支付等进行充值。支付功能本身并不收集您的个人信息,但我们需要将您的订单号与交易金额信息与这些支付机构共享以实现其确认您的支付指令并完成支付。
+ (2)为充分保障您的合法权益,我们允许您将账户余额提现。在此过程中,您至少需要向我们提供微信账号、支付宝账号、或汇款银行名称、汇款账号、汇款账户名、汇款金额等信息。 +
+
+ +
+
二、设备权限调用
+
+ 在您使用我们的产品及服务过程中,我们需要在必要范围内向您申请获取设备权限。请您知悉,我们不会默认开启您设备的权限,仅在您主动确认开启的情况下,我们才有可能通过设备权限收集您的信息。
+ 我们的产品及服务调用的权限、调用权限的目的,以及调用权限前向您询问的情况请见下表: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
设备权限调用权限的目的是否询问可否关闭
网络访问网络
外部存储存储应用运行所需文件不可
麦克风录音
系统设置设置铃音
+
+
+ +
+
三、我们如何使用您的个人信息
+
+ (1)我们会根据本政策的约定并为实现我们的服务的目的对所收集的个人信息进行使用。
+ (2)请您注意,您在使用我们提供的服务时所提供的所有个人信息,除非您删除或通过系统设置拒绝我们收集,否则将在您使用我们的服务期间持续授权我们使用。在您注销账号时,我们将停止使用并删除您的个人信息。
+ (3)我们会对我们提供的服务使用情况进行统计,并可能会与公众或第三方共享这些统计信息,以用于产品开发、服务优化、安全保障的目的。但这些统计信息不包含您的任何身份识别信息。
+ (4)当我们要将您的个人信息用于本政策未载明的其它用途时,或基于特定目的收集而来的信息用于其他目的时,会通过您主动做出勾选的形式事先征求您的同意。 +
+
+ +
+
四、我们如何使用Cookie和同类技术
+
+ (1)为确保网站正常运转,我们有时会在计算机或移动设备上存储名为 Cookie 的小数据文件。
+ (2)我们启用Cookie的目的与大多数网站的目的一样,即改善用户体验。借助于 Cookie,网站能够记住用户的单次访问(使用会话 Cookie)或多次访问(使用永久 Cookie)。
+         借助于 Cookie,网站能够保存设置,例如计算机或移动设备的语言、字体大小和其他浏览偏好。这意味着,用户无需在每次访问时重新配置用户偏好设置。
+         如果某个网站不使用 Cookie,那么在用户每一次打开网页时,该网站都会将其视为新访客。例如,如果您登录某个网站后转到另一个网页,该网站就不会识别出您,而您会被再次注销。
+ (3)我们不会将 Cookie 用于本政策所述目的之外的任何用途。
+ (4)您可根据自己的偏好管理或删除 Cookie。您可以清除计算机上保存的所有 Cookie,大部分网络浏览器都设有阻止 Cookie 的功能。但如果您这么做,则需要在每一次访问我们的网站时亲自更改用户设置。 +
+
+ +
+
五、我们如何披露您的个人信息
+
+ 通常我们不会对外披露您的个人信息,但以下场景除外:
+ (1)在某些服务由我们的授权合作伙伴提供的情况下,我们会如本政策描述与该合作伙伴共享您的个人信息。例如,委托合作伙伴代表我们处理热线、发送电子邮件、提供技术支持等,我们会要求合作伙伴仅能将您的个人信息用于代表我们向您提供服务。
+ (2)在适用的法律要求或响应法律程序的情况下,我们也可能会向相关的执法机关或者其他政府机关披露您的个人信息。在某些管辖区,如果我们牵涉到重组、合并或破产和清理诉讼,那么您的个人信息还可能会被披露给交易方。
+ (3)我们还会在合理的情况下披露您的数据,例如出于执行合同以及我们认为为阻止身体损害或财产损失或调查可能的或实际的非法行为有必要披露且披露是适当的。 +
+
+ +
+
六、我们如何保护和留存您的个人信息
+
+ (1)我们重视个人信息的安全,采用适当的物理、管理和技术保障措施来保护您的个人信息不被未经授权访问、披露、使用、修改、损坏或丢失。
+         例如,我们会使用加密技术确保数据的机密性;我们会使用保护机制防止数据遭到恶意攻击;我们会部署访问控制机制,确保只有授权人员才可访问个人信息;以及我们会举办安全和隐私保护培训课程,加强员工对于保护个人信息重要性的认识。
+         我们会尽力保护您的个人信息,但是请注意任何安全措施都无法做到无懈可击。
+ (2)我们将会在达成本政策所述目的所需的期限内保留您的个人信息,除非按照法律要求或许可需要延长保留期或受到法律的允许。因为基于不同的场景和产品及服务的不同,数据的存储期可能会有所不同。
+         我们用于确定存留期的标准包括:完成该业务目的需要留存个人信息的时间,包括提供产品和服务,维护相应的交易及业务记录,管控并提升产品与服务性能与质量,保证产品和服务的安全,应对可能的用户查询或投诉,问题定位等;用户是否同意更长的留存期间;法律、合同等是否有保留数据的特殊要求等。
+         只要您的账户是为您提供服务必须,我们都将保留您的注册信息。您也可以选择注销您的账号,在您注销账号后,我们会停止基于该账号提供产品和服务,并在无特殊法律要求的情况下,删除您相应的个人信息或对其进行匿名化处理。 +
+
+ +
+
七、我们如何保护儿童的个人信息
+
+ (1)我们的产品和服务主要面向成人。如果没有父母或监护人的同意,(未满14周岁的未成年人)不得创建自己的用户帐户。
+ (2)对于经父母同意而收集儿童个人信息的情况,我们只会在受到法律允许、父母或监护人明确同意或者保护儿童所必要的情况下使用或披露此数据。
+ (3)如果我们发现在未事先获得可证实的父母同意的情况下收集了儿童的个人信息,则会设法尽快删除相关数据。 +
+
+ +
+
八、本政策如何更新
+
+ 我们保留不时更新或修改本政策的权利。如果我们的隐私政策变更,我们会将最新版隐私政策发布在这里。
+ 如果我们对隐私政策做出了重大变更,我们还可能会通过不同渠道向您发送变更通知,例如,在我们的网站上发布通知或者向您发布单独的通知。 +
+
+ +
+
九、其他
+
+ (1)本政策的订立、执行和解释及争议的解决均应适用中国法律。
+ (2)如双方就本协议内容或其执行发生任何争议,双方应尽量友好协商解决;协商不成时,任何一方均可向唱作网所在地的人民法院提起诉讼。 +
+
+ +
+ +
+ + + diff --git a/public/html/user.html b/public/html/user.html new file mode 100644 index 0000000..dd514e1 --- /dev/null +++ b/public/html/user.html @@ -0,0 +1,120 @@ + + +
+
唱作网用户协议
+ +
+
+
+ 欢迎您使用唱作网产品及服务!
+ 为使用唱作网产品及服务(以下统称“本产品及服务”),您应当阅读并遵守《唱作网用户协议》(以下简称“本协议”),以及《唱作网隐私政策》
+ 在您注册成为唱作网用户的过程中,您需要完成我们的注册流程并通过点击同意的形式在线签署以下使用协议,请务必仔细阅读,充分理解协议中的条款后再点击同意,这些条款可能会明确您应履行的义务或对您的权利有所限制。 +
+
+ +
+
服务内容
+
+ (1)本产品及服务的具体内容由唱作网根据实际情况提供,例如账户管理、软件升级等。唱作网保留随时变更、中断或终止部分或全部产品及服务的权利。
+ (2)当您使用本产品时,您应选择与您的终端、系统等相匹配的本产品版本,否则,您可能无法正常使用本产品。 + (3)唱作网在提供产品及服务时,可能会对部分产品及服务收取一定的费用。在此情况下, 唱作网会在相关页面上做明确的提示。如用户拒绝支付该等费用,则不能使用相关产品及服务。
+ (4)唱作网仅提供相关的产品及服务,除此之外与之有关的设备(如电脑、移动设备、调制解调器及其他与接入互联网有关的装置)及所需的费用( 如为接入互联网而支付的电话费及上网费 )均应由用户自行负担。 +
+
+ +
+
使用规则
+
+ (1)当您注册成功后,唱作网将给予您一个用户帐号及相应的密码,该用户帐号和密码由您负责保管,您应当对以其用户帐号进行的所有活动和事件负法律责任。
+ (2)在您使用本产品与服务视为同意接受唱作网通过电子邮件或其它方式向用户发送的商品促销或其他相关商业信息。
+ (3)您在使用本服务过程中,必须遵循以下原则:
+         A:遵守中国有关的法律和法规;
+         B:不得为任何非法目的而使用网络服务系统;
+         C:遵守所有与本产品及服务有关的网络协议、规定和程序;
+         D:不得利用本产品及服务进行任何可能对互联网的正常运转造成不利影响的行为;
+         E:不得利用本产品及服务传播任何骚扰性的、中伤他人的、辱骂性的、恐吓性的、庸俗淫秽的或其他任何非法的信息资料;
+         F:不得利用本产品及服务进行任何不利于唱作网的行为;
+         G:如发现任何非法使用用户帐号或帐号出现安全漏洞的情况,应立即通告唱作网。 +
+
+ +
+
内容所有权
+
+ (1)唱作网提供的网络服务内容可能包括:文字、软件、声音、图片、MV、图表等。所有这些内容受版权、商标和其它财产所有权法律的保护。
+ (2)您只有在获得唱作网或其他相关权利人的授权之后才能使用这些内容,而不能擅自复制、再造这些内容、或创造与内容有关的派生产品。
+ (3)您使用本产品及服务创作或制作的歌曲,著作权仍归您所有。唱作网对您著作权部分不作任何要求。
+
+
+ +
+
隐私保护
+
+ 隐私保护相关条款以《唱作网隐私政策》内容为准。 +
+
+ +
+
免责声明
+
+ (1)您明确同意其使用本产品及服务所存在的风险将完全由其您承担,因您使用本产品及服务而产生的一切后果也由您承担, 唱作网对此不承担任何责任。
+ (2)唱作网不担保本产品及服务一定能满足您的要求,也不担保本产品及服务不会中断,对本产品及服务的及时性、安全性、准确性也都不作担保。
+
+
+ +
+
服务变更、中断或终止
+
+ (1)如因系统维护或升级的需要而需暂停网络服务, 唱作网将尽可能事先进行通告。
+ (2)如发生下列任何一种情形, 唱作网有权随时中断或终止向您提供本协议中的服务而无需通知:
+         A:您提供的个人资料不真实;
+         B:您违反本协议中规定的使用规则。
+ (3)除前款所述情形外, 唱作网同时保留在不事先通知您的情况下随时中断或终止部分或全部产品及服务的权利,对于所有服务的中断或终止而造成的任何损失, 唱作网无须承担责任。 +
+
+ +
+
违约赔偿
+
+ 您同意保障和维护唱作网及其他用户的利益,如因您违反有关法律、法规或本协议项下的任何条款而给唱作网或任何其他第三人造成损失,您同意承担由此造成的损害赔偿责任。 +
+
+ +
+
修改协议
+
+ (1)唱作网将可能不时的修改本协议的有关条款,一旦条款内容发生变动,唱作网将尽量在相关页面提示修改内容。
+ (2)如果不同意唱作网对服务条款所做的修改,您有权停止使用本产品及服务。如果您继续使用本产品及服务,则视为您接受服务条款的变动。 +
+
+ +
+
法律管辖
+
+ (1)本协议的订立、执行和解释及争议的解决均应适用中国法律。
+ (2)如双方就本协议内容或其执行发生任何争议,双方应尽量友好协商解决;协商不成时,任何一方均可向唱作网所在地的人民法院提起诉讼。 +
+
+ +
+
通知和送达
+
+ 本协议项下所有的通知均可通过重要页面公告、电子邮件或常规的信件传送等方式进行;该等通知于发送之日视为已送达收件人。 +
+
+ +
+
其他规定
+
+ (1)本协议构成双方对本协议之约定事项及其他有关事宜的完整协议,除本协议规定的之外,未赋予本协议各方其他权利。
+ (2)如本协议中的任何条款无论因何种原因完全或部分无效或不具有执行力,本协议的其余条款仍应有效并且有约束力。
+ (3)本协议中的标题仅为方便而设,在解释本协议时应被忽略。 +
+
+ +
+ +
+ + + diff --git a/public/html/vip.html b/public/html/vip.html new file mode 100644 index 0000000..488a64f --- /dev/null +++ b/public/html/vip.html @@ -0,0 +1,33 @@ + + +
+
VIP会员
+ +
+
特权:
+
+ (1)唱作魔方所有版本免收功能费;
+ (2)唱作魔方所有版本使用收费风格时享受8折优惠;
+ (3)唱作魔方所有版本可使用离线保存功能;
+
+ 更多VIP会员特权敬请期待! +
+
+ +
+
费用标准:
+
+ (1)一月:1000 音符
+ (2)半年:5500 音符
+ (3)一年:10000 音符
+ (4)两年:20000 音符
+ (5)终身:30000 音符(为庆祝唱作网首次推出VIP会员服务,限时可成为终身会员。目前已结束。) +
+
+ +
+ +
+ + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..c2ad224 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/api/auth.ts b/src/api/auth.ts new file mode 100644 index 0000000..f28b5bf --- /dev/null +++ b/src/api/auth.ts @@ -0,0 +1,150 @@ +import request from "@/utils/request"; +import { verify } from "crypto"; + +const AUTH_BASE_URL = "/api/v1/auth"; + +class AuthAPI { + /** 登录 接口*/ + static login(data: LoginData) { + const formData = new FormData(); + formData.append("username", data.username); + formData.append("password", data.password); + formData.append("captchaKey", data.captchaKey); + formData.append("captchaCode", data.captchaCode); + + return request({ + url: `${AUTH_BASE_URL}/login`, + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + /** 注销 接口*/ + static logout() { + return request({ + url: `${AUTH_BASE_URL}/logout`, + method: "delete", + }); + } + + /** 获取验证码 接口*/ + static getCaptcha() { + return request({ + url: `${AUTH_BASE_URL}/captcha`, + method: "get", + }); + } + + static sendCode(data: SendCodeData) { + const formData = new FormData(); + formData.append("contact", data.contact); + formData.append("captchaKey", data.captchaKey); + formData.append("captchaCode", data.captchaCode); + + return request({ + url: `${AUTH_BASE_URL}/sendRegCode`, + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + /** 登录 接口*/ + static register(data: RegisterData) { + const formData = new FormData(); + formData.append("username", data.username); + formData.append("password", data.password); + formData.append("verifyCode", data.verifyCode); + + return request({ + url: `${AUTH_BASE_URL}/register`, + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + static autoLogin(username: string, password: string) { + const formData = new FormData(); + formData.append("username", username); + formData.append("password", password); + + return request({ + url: `${AUTH_BASE_URL}/autoLogin`, + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } +} + +export default AuthAPI; + +/** 登录请求参数 */ +export interface LoginData { + /** 用户名 */ + username: string; + /** 密码 */ + password: string; + /** 验证码缓存key */ + captchaKey: string; + /** 验证码 */ + captchaCode: string; +} + +/** 登录响应 */ +export interface LoginResult { + /** 访问token */ + accessToken?: string; + /** 过期时间(单位:毫秒) */ + expires?: number; + /** 刷新token */ + refreshToken?: string; + /** token 类型 */ + tokenType?: string; +} + +/** 验证码响应 */ +export interface CaptchaResult { + /** 验证码缓存key */ + captchaKey: string; + /** 验证码图片Base64字符串 */ + captchaBase64: string; +} + +export interface CaptchaData { + /** 验证码缓存key */ + captchaKey: string; + /** 验证码 */ + captchaCode: string; +} + +export interface SendCodeData { + /** 发送验证码账号 */ + contact: string; + /** 验证码缓存key */ + captchaKey: string; + /** 验证码 */ + captchaCode: string; +} + +/** 注册请求参数 */ +export interface RegisterData { + /** 用户名 */ + username: string; + /** 密码 */ + password: string; + /** 重复输入密码 */ + repeatPassword: string; + /** 验证码 */ + verifyCode: string; +} diff --git a/src/api/bank.ts b/src/api/bank.ts new file mode 100644 index 0000000..19b2260 --- /dev/null +++ b/src/api/bank.ts @@ -0,0 +1,28 @@ +import request from "@/utils/request"; + +const BANK_BASE_URL = "/api/v1/bank"; + +class BankAPI { + static hotlist() { + return request({ + url: `${BANK_BASE_URL}/hotList`, + method: "get", + }); + } + + static bankInfo() { + return request({ + url: `${BANK_BASE_URL}/me`, + method: "get", + }); + } +} + +export default BankAPI; + +export interface BankInfo { + userId: number; + bankName?: string; + bankCardNum?: string; + bankCardName?: string; +} diff --git a/src/api/codegen.ts b/src/api/codegen.ts new file mode 100644 index 0000000..166aa22 --- /dev/null +++ b/src/api/codegen.ts @@ -0,0 +1,191 @@ +import request from "@/utils/request"; + +const GENERATOR_BASE_URL = "/api/v1/codegen"; + +class GeneratorAPI { + /** 获取数据表分页列表 */ + static getTablePage(params: TablePageQuery) { + return request>({ + url: `${GENERATOR_BASE_URL}/table/page`, + method: "get", + params: params, + }); + } + + /** 获取代码生成配置 */ + static getGenConfig(tableName: string) { + return request({ + url: `${GENERATOR_BASE_URL}/${tableName}/config`, + method: "get", + }); + } + + /** 获取代码生成配置 */ + static saveGenConfig(tableName: string, data: GenConfigForm) { + return request({ + url: `${GENERATOR_BASE_URL}/${tableName}/config`, + method: "post", + data: data, + }); + } + + /** 获取代码生成预览数据 */ + static getPreviewData(tableName: string) { + return request({ + url: `${GENERATOR_BASE_URL}/${tableName}/preview`, + method: "get", + }); + } + + /** 重置代码生成配置 */ + static resetGenConfig(tableName: string) { + return request({ + url: `${GENERATOR_BASE_URL}/${tableName}/config`, + method: "delete", + }); + } + + /** + * 下载 ZIP 文件 + * @param url + * @param fileName + */ + static download(tableName: string) { + return request({ + url: `${GENERATOR_BASE_URL}/${tableName}/download`, + method: "get", + responseType: "blob", + }).then((response) => { + const fileName = decodeURI( + response.headers["content-disposition"].split(";")[1].split("=")[1] + ); + + const blob = new Blob([response.data], { type: "application/zip" }); + const a = document.createElement("a"); + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = fileName; + a.click(); + window.URL.revokeObjectURL(url); + }); + } +} + +export default GeneratorAPI; + +/** 代码生成预览对象 */ +export interface GeneratorPreviewVO { + /** 文件生成路径 */ + path: string; + /** 文件名称 */ + fileName: string; + /** 文件内容 */ + content: string; +} + +/** 数据表分页查询参数 */ +export interface TablePageQuery extends PageQuery { + /** 关键字(表名) */ + keywords?: string; +} + +/** 数据表分页对象 */ +export interface TablePageVO { + /** 表名称 */ + tableName: string; + + /** 表描述 */ + tableComment: string; + + /** 存储引擎 */ + engine: string; + + /** 字符集排序规则 */ + tableCollation: string; + + /** 创建时间 */ + createTime: string; +} + +/** 代码生成配置表单 */ +export interface GenConfigForm { + /** 主键 */ + id?: number; + + /** 表名 */ + tableName?: string; + + /** 业务名 */ + businessName?: string; + + /** 模块名 */ + moduleName?: string; + + /** 包名 */ + packageName?: string; + + /** 实体名 */ + entityName?: string; + + /** 作者 */ + author?: string; + + /** 上级菜单 */ + parentMenuId?: number; + + /** 后端应用名 */ + backendAppName?: string; + /** 前端应用名 */ + frontendAppName?: string; + + /** 字段配置列表 */ + fieldConfigs?: FieldConfig[]; +} + +/** 字段配置 */ +export interface FieldConfig { + /** 主键 */ + id?: number; + + /** 列名 */ + columnName?: string; + + /** 列类型 */ + columnType?: string; + + /** 字段名 */ + fieldName?: string; + + /** 字段类型 */ + fieldType?: string; + + /** 字段描述 */ + fieldComment?: string; + + /** 是否在列表显示 */ + isShowInList?: number; + + /** 是否在表单显示 */ + isShowInForm?: number; + + /** 是否在查询条件显示 */ + isShowInQuery?: number; + + /** 是否必填 */ + isRequired?: number; + + /** 表单类型 */ + formType?: number; + + /** 查询类型 */ + queryType?: number; + + /** 字段长度 */ + maxLength?: number; + + /** 字段排序 */ + fieldSort?: number; + + /** 字典类型 */ + dictType?: string; +} diff --git a/src/api/config.ts b/src/api/config.ts new file mode 100644 index 0000000..72760d7 --- /dev/null +++ b/src/api/config.ts @@ -0,0 +1,104 @@ +import request from "@/utils/request"; + +const CONFIG_BASE_URL = "/api/v1/config"; + +class ConfigAPI { + /** 获取系统配置分页数据 */ + static getPage(queryParams?: ConfigPageQuery) { + return request>({ + url: `${CONFIG_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + /** + * 获取系统配置表单数据 + * + * @param id ConfigID + * @returns Config表单数据 + */ + static getFormData(id: number) { + return request({ + url: `${CONFIG_BASE_URL}/${id}/form`, + method: "get", + }); + } + + /** 添加系统配置*/ + static add(data: ConfigForm) { + return request({ + url: `${CONFIG_BASE_URL}`, + method: "post", + data: data, + }); + } + + /** + * 更新系统配置 + * + * @param id ConfigID + * @param data Config表单数据 + */ + static update(id: number, data: ConfigForm) { + return request({ + url: `${CONFIG_BASE_URL}/${id}`, + method: "put", + data: data, + }); + } + + /** + * 删除系统配置 + * + * @param ids 系统配置ID + */ + static deleteById(id: number) { + return request({ + url: `${CONFIG_BASE_URL}/${id}`, + method: "delete", + }); + } + + static refreshCache() { + return request({ + url: `${CONFIG_BASE_URL}`, + method: "patch", + }); + } +} + +export default ConfigAPI; + +/** $系统配置分页查询参数 */ +export interface ConfigPageQuery extends PageQuery { + /** 搜索关键字 */ + keywords?: string; +} + +/** 系统配置表单对象 */ +export interface ConfigForm { + /** 主键 */ + id?: number; + /** 配置名称 */ + configName?: string; + /** 配置键 */ + configKey?: string; + /** 配置值 */ + configValue?: string; + /** 描述、备注 */ + remark?: string; +} + +/** 系统配置分页对象 */ +export interface ConfigPageVO { + /** 主键 */ + id?: number; + /** 配置名称 */ + configName?: string; + /** 配置键 */ + configKey?: string; + /** 配置值 */ + configValue?: string; + /** 描述、备注 */ + remark?: string; +} diff --git a/src/api/dict.ts b/src/api/dict.ts new file mode 100644 index 0000000..83cd675 --- /dev/null +++ b/src/api/dict.ts @@ -0,0 +1,192 @@ +import request from "@/utils/request"; + +const DICT_BASE_URL = "/api/v1/dict"; + +class DictAPI { + /** + * 获取字典分页列表 + * + * @param queryParams 查询参数 + * @returns 字典分页结果 + */ + static getPage(queryParams: DictPageQuery) { + return request>({ + url: `${DICT_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + + /** + * 获取字典表单数据 + * + * @param id 字典ID + * @returns 字典表单数据 + */ + static getFormData(id: number) { + return request>({ + url: `${DICT_BASE_URL}/${id}/form`, + method: "get", + }); + } + + /** + * 新增字典 + * + * @param data 字典表单数据 + */ + static add(data: DictForm) { + return request({ + url: `${DICT_BASE_URL}`, + method: "post", + data: data, + }); + } + + /** + * 修改字典 + * + * @param id 字典ID + * @param data 字典表单数据 + */ + static update(id: number, data: DictForm) { + return request({ + url: `${DICT_BASE_URL}/${id}`, + method: "put", + data: data, + }); + } + + /** + * 删除字典 + * + * @param ids 字典ID,多个以英文逗号(,)分隔 + */ + static deleteByIds(ids: string) { + return request({ + url: `${DICT_BASE_URL}/${ids}`, + method: "delete", + }); + } + + /** + * 获取字典列表 + * + * @returns 字典列表 + */ + static getList() { + return request({ + url: `${DICT_BASE_URL}/list`, + method: "get", + }); + } + + /** + * 获取字典的数据项 + * + * @param typeCode 字典编码 + * @returns 字典数据项 + */ + static getOptions(code: string) { + return request({ + url: `${DICT_BASE_URL}/${code}/options`, + method: "get", + }); + } +} + +export default DictAPI; + +/** + * 字典查询参数 + */ +export interface DictPageQuery extends PageQuery { + /** + * 关键字(字典名称/编码) + */ + keywords?: string; +} + +/** + * 字典分页对象 + */ +export interface DictPageVO { + /** + * 字典ID + */ + id: number; + /** + * 字典名称 + */ + name: string; + /** + * 字典编码 + */ + code: string; + /** + * 字典状态(1-启用,0-禁用) + */ + status: number; + /** + * 字典项列表 + */ + dictItems: DictItem[]; +} + +/** + * 字典项 + */ +export interface DictItem { + /** + * 字典项ID + */ + id?: number; + /** + * 字典项名称 + */ + name?: string; + /** + * 字典项值 + */ + value?: string; + /** + * 排序 + */ + sort?: number; + /** + * 状态(1-启用,0-禁用) + */ + status?: number; +} + +// TypeScript 类型声明 + +/** + * 字典 + */ +export interface DictForm { + /** + * 字典ID + */ + id?: number; + /** + * 字典名称 + */ + name?: string; + /** + * 字典编码 + */ + code?: string; + /** + * 字典状态(1-启用,0-禁用) + */ + status?: number; + /** + * 备注 + */ + remark?: string; + /** + * 字典数据项列表 + */ + dictItems?: DictItem[]; +} diff --git a/src/api/file.ts b/src/api/file.ts new file mode 100644 index 0000000..c812520 --- /dev/null +++ b/src/api/file.ts @@ -0,0 +1,74 @@ +import request from "@/utils/request"; + +class FileAPI { + /** + * 文件上传地址 + */ + static uploadUrl = import.meta.env.VITE_APP_BASE_API + "/api/v1/files"; + + /** + * 上传文件 + * + * @param file + */ + static upload(file: File, type: string) { + const formData = new FormData(); + formData.append("file", file); + formData.append("type", type); + return request({ + url: "/api/v1/files", + method: "post", + timeout: 300000, + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + /** + * 删除文件 + * + * @param filePath 文件完整路径 + */ + static deleteByPath(filePath?: string) { + return request({ + url: "/api/v1/files", + method: "delete", + params: { filePath: filePath }, + }); + } + + /** + * 下载文件 + * @param url + * @param fileName + */ + static downloadFile(url: string, fileName?: string) { + return request({ + url: url, + method: "get", + responseType: "blob", + }).then((res) => { + const blob = new Blob([res.data]); + const a = document.createElement("a"); + const url = window.URL.createObjectURL(blob); + a.href = url; + a.download = fileName || "下载文件"; + a.click(); + window.URL.revokeObjectURL(url); + }); + } +} + +export default FileAPI; + +/** + * 文件API类型声明 + */ +export interface FileInfo { + /** 文件名 */ + name: string; + /** 文件路径 */ + url: string; +} diff --git a/src/api/helper.ts b/src/api/helper.ts new file mode 100644 index 0000000..4f74dd5 --- /dev/null +++ b/src/api/helper.ts @@ -0,0 +1,86 @@ +import request from "@/utils/request"; +import { ColoredType } from "@/enums/SoftwareEnum"; + +const HELPER_BASE_URL = "/api/v1/helper"; + +class HelpAPI { + static listQnA(id: number) { + return request({ + url: `${HELPER_BASE_URL}/listQnA/${id}`, + method: "get", + }); + } + + static getPage(queryParams: QnAPageQuery) { + return request>({ + url: `${HELPER_BASE_URL}/pageQnA`, + method: "get", + params: queryParams, + }); + } + + static setByIds(ids: string, state: number) { + return request({ + url: `${HELPER_BASE_URL}/setQnAByIds/${ids}`, + method: "put", + params: { state: state }, + }); + } + + static saveOrUpdate(data: UpdateQnA) { + return request({ + url: `${HELPER_BASE_URL}/updateQnA`, + method: "put", + data: data, + }); + } +} + +export default HelpAPI; + +export interface QnAPageQuery extends PageQuery { + userId?: number; + state?: number; + typeIds?: string; +} + +/** 问题简要信息 */ +export interface HelpQnA { + /** ID */ + helpId: number; + question: string; + answer: string; +} + +// export interface QnAType { +// color: string; +// value: string; +// label: string; +// } + +/** 问题简要信息 */ +export interface FullQnA { + /** ID */ + helpId?: number; + question?: string; + answer?: string; + helpTypeId?: string; //提问类型 + types?: ColoredType[]; + submitVersinos?: string; //提交问题的软件版本 + userId?: number; //提问用户 + userName?: string; + nickname?: string; + state?: number; //编辑状态 + editTime?: number; //提交时间 + answerTime?: number; //处理时间 +} + +/** 问题简要信息 */ +export interface UpdateQnA { + /** ID */ + helpId?: number; + question?: string; + answer?: string; + helpTypeId?: string; + state?: number; +} diff --git a/src/api/integral.ts b/src/api/integral.ts new file mode 100644 index 0000000..fe5fa9d --- /dev/null +++ b/src/api/integral.ts @@ -0,0 +1,56 @@ +import request from "@/utils/request"; + +const INTEGRAL_BASE_URL = "/api/v1/integral"; + +class IntegralAPI { + static listAdmin(query: IntegralPageQuery) { + return request>({ + url: `${INTEGRAL_BASE_URL}/pageAdmin`, + method: "get", + params: query, + }); + } + + static listUser(query: IntegralPageQuery) { + return request>({ + url: `${INTEGRAL_BASE_URL}/pageUser`, + method: "get", + params: query, + }); + } +} + +export default IntegralAPI; + +export interface IntegralPageAdminVO { + detailsId: number; + userId: number; + userName: string; + nickName?: string; + title: string; + score: number; + balance: number; + //类型规则编号(-1:订制音乐服务;-2:退订音乐服务;-3:音乐服务收入;-4:保存音频;-5:保存midi;-6:积分发放;-7:购买VIP;;-8:提现;-9:推荐音乐;-10:风格收入;-11:购买CPJ;-12:出售CPJ;-13:购买金曲票数;-14:领奖;-15:论坛金钱兑换) + ruleid: number; + ruletype: number; + addtime: number; + comment: string; +} + +export interface IntegralPageUserVO { + detailsId: number; + title: string; + score: number; + balance: number; + ruleid: number; + ruletype: number; + addtime: number; + comment: string; +} + +export interface IntegralPageQuery extends PageQuery { + userId?: number; + ruletype?: number; + /** 添加时间 */ + addtime?: [number, number]; +} diff --git a/src/api/log.ts b/src/api/log.ts new file mode 100644 index 0000000..a8c71e5 --- /dev/null +++ b/src/api/log.ts @@ -0,0 +1,122 @@ +import request from "@/utils/request"; + +const LOG_BASE_URL = "/api/v1/logs"; + +class LogAPI { + /** + * 获取日志分页列表 + * + * @param queryParams 查询参数 + */ + static getPage(queryParams: LogPageQuery) { + return request>({ + url: `${LOG_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + + /** + * 获取访问趋势 + * + * @param queryParams + * @returns + */ + static getVisitTrend(queryParams: VisitTrendQuery) { + return request({ + url: `${LOG_BASE_URL}/visit-trend`, + method: "get", + params: queryParams, + }); + } + + /** + * 获取访问趋势 + * + * @param queryParams + * @returns + */ + static getVisitStats() { + return request({ + url: `${LOG_BASE_URL}/visit-stats`, + method: "get", + }); + } +} + +export default LogAPI; + +/** + * 日志分页查询对象 + */ +export interface LogPageQuery extends PageQuery { + /** 搜索关键字 */ + keywords?: string; + /** 操作时间 */ + createTime?: [string, string]; +} + +/** + * 系统日志分页VO + */ +export interface LogPageVO { + /** 主键 */ + id: number; + /** 日志模块 */ + module: string; + /** 日志内容 */ + content: string; + /** 请求路径 */ + requestUri: string; + /** 请求方法 */ + method: string; + /** IP 地址 */ + ip: string; + /** 地区 */ + region: string; + /** 浏览器 */ + browser: string; + /** 终端系统 */ + os: string; + /** 执行时间(毫秒) */ + executionTime: number; + /** 操作人 */ + operator: string; +} + +/** 访问趋势视图对象 */ +export interface VisitTrendVO { + /** 日期列表 */ + dates: string[]; + /** 浏览量(PV) */ + pvList: number[]; + /** 访客数(UV) */ + uvList: number[]; + /** IP数 */ + ipList: number[]; +} + +/** 访问趋势查询参数 */ +export interface VisitTrendQuery { + /** 开始日期 */ + startDate: string; + /** 结束日期 */ + endDate: string; +} + +/** 访问统计 */ +export interface VisitStatsVO { + /** 标题 */ + title: string; + /** 类型 */ + type: "pv" | "uv" | "ip"; + + /** 今日访问量 */ + todayCount: number; + /** 总访问量 */ + totalCount: number; + /** 同比增长率(相对于昨天同一时间段的增长率) */ + growthRate: number; + + totalCountOutput: number; +} diff --git a/src/api/menu.ts b/src/api/menu.ts new file mode 100644 index 0000000..b2fb9ba --- /dev/null +++ b/src/api/menu.ts @@ -0,0 +1,209 @@ +import request from "@/utils/request"; +// 菜单基础URL +const MENU_BASE_URL = "/api/v1/menus"; + +class MenuAPI { + /** + * 获取当前用户的路由列表 + *

+ * 无需传入角色,后端解析token获取角色自行判断是否拥有路由的权限 + * + * @returns 路由列表 + */ + static getRoutes() { + return request({ + url: `${MENU_BASE_URL}/routes`, + method: "get", + }); + } + + /** + * 获取菜单树形列表 + * + * @param queryParams 查询参数 + * @returns 菜单树形列表 + */ + static getList(queryParams: MenuQuery) { + return request({ + url: `${MENU_BASE_URL}`, + method: "get", + params: queryParams, + }); + } + + /** + * 获取菜单下拉数据源 + * + * @returns 菜单下拉数据源 + */ + static getOptions(onlyParent?: boolean) { + return request({ + url: `${MENU_BASE_URL}/options`, + method: "get", + params: { onlyParent: onlyParent }, + }); + } + + /** + * 获取菜单表单数据 + * + * @param id 菜单ID + */ + static getFormData(id: number) { + return request({ + url: `${MENU_BASE_URL}/${id}/form`, + method: "get", + }); + } + + /** + * 添加菜单 + * + * @param data 菜单表单数据 + * @returns 请求结果 + */ + static add(data: MenuForm) { + return request({ + url: `${MENU_BASE_URL}`, + method: "post", + data: data, + }); + } + + /** + * 修改菜单 + * + * @param id 菜单ID + * @param data 菜单表单数据 + * @returns 请求结果 + */ + static update(id: string, data: MenuForm) { + return request({ + url: `${MENU_BASE_URL}/${id}`, + method: "put", + data: data, + }); + } + + /** + * 删除菜单 + * + * @param id 菜单ID + * @returns 请求结果 + */ + static deleteById(id: number) { + return request({ + url: `${MENU_BASE_URL}/${id}`, + method: "delete", + }); + } +} + +export default MenuAPI; + +import { MenuTypeEnum } from "@/enums/MenuTypeEnum"; + +/** 菜单查询参数 */ +export interface MenuQuery { + /** 搜索关键字 */ + keywords?: string; +} + +/** 菜单视图对象 */ +export interface MenuVO { + /** 子菜单 */ + children?: MenuVO[]; + /** 组件路径 */ + component?: string; + /** ICON */ + icon?: string; + /** 菜单ID */ + id?: number; + /** 菜单名称 */ + name?: string; + /** 父菜单ID */ + parentId?: number; + /** 按钮权限标识 */ + perm?: string; + /** 跳转路径 */ + redirect?: string; + /** 路由名称 */ + routeName?: string; + /** 路由相对路径 */ + routePath?: string; + /** 菜单排序(数字越小排名越靠前) */ + sort?: number; + /** 菜单 */ + type?: MenuTypeEnum; + /** 菜单是否可见(1:显示;0:隐藏) */ + visible?: number; +} + +/** 菜单表单对象 */ +export interface MenuForm { + /** 菜单ID */ + id?: string; + /** 父菜单ID */ + parentId?: number; + /** 菜单名称 */ + name?: string; + /** 菜单是否可见(1-是 0-否) */ + visible: number; + /** ICON */ + icon?: string; + /** 排序 */ + sort?: number; + /** 路由名称 */ + routeName?: string; + /** 路由路径 */ + routePath?: string; + /** 组件路径 */ + component?: string; + /** 跳转路由路径 */ + redirect?: string; + /** 菜单 */ + type?: MenuTypeEnum; + /** 权限标识 */ + perm?: string; + /** 【菜单】是否开启页面缓存 */ + keepAlive?: number; + /** 【目录】只有一个子路由是否始终显示 */ + alwaysShow?: number; + /** 参数 */ + params?: KeyValue[]; +} + +interface KeyValue { + key: string; + value: string; +} + +/** RouteVO,路由对象 */ +export interface RouteVO { + /** 子路由列表 */ + children: RouteVO[]; + /** 组件路径 */ + component?: string; + /** 路由属性 */ + meta?: Meta; + /** 路由名称 */ + name?: string; + /** 路由路径 */ + path?: string; + /** 跳转链接 */ + redirect?: string; +} + +/** Meta,路由属性 */ +export interface Meta { + /** 【目录】只有一个子路由是否始终显示 */ + alwaysShow?: boolean; + /** 是否隐藏(true-是 false-否) */ + hidden?: boolean; + /** ICON */ + icon?: string; + /** 【菜单】是否开启页面缓存 */ + keepAlive?: boolean; + /** 路由title */ + title?: string; +} diff --git a/src/api/message.ts b/src/api/message.ts new file mode 100644 index 0000000..6b30962 --- /dev/null +++ b/src/api/message.ts @@ -0,0 +1,57 @@ +import request from "@/utils/request"; + +const MESSAGE_BASE_URL = "/api/v1/message"; + +class MessageAPI { + static getPage(queryParams: MessagePageQuery) { + return request>({ + url: `${MESSAGE_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + static getLatest(userId: number) { + return request({ + url: `${MESSAGE_BASE_URL}/${userId}/latest`, + method: "get", + }); + } + static setByIds(ids: string, state: number) { + return request({ + url: `${MESSAGE_BASE_URL}/${ids}`, + method: "put", + params: { state: state }, + }); + } + + static save(data: WebMessage) { + return request({ + url: `${MESSAGE_BASE_URL}/save`, + method: "put", + data: data, + }); + } +} + +export default MessageAPI; + +export interface MessagePageQuery extends PageQuery { + userId?: number; + type: number; +} + +export interface WebMessage { + msgId?: number; + + userId?: number; + + title?: string; + content?: string; + + state?: number; + type?: number; + + sendTime?: number; + readTime?: number; + deleteTime?: number; +} diff --git a/src/api/notice.ts b/src/api/notice.ts new file mode 100644 index 0000000..8ad9be6 --- /dev/null +++ b/src/api/notice.ts @@ -0,0 +1,48 @@ +import request from "@/utils/request"; + +const NOTICE_BASE_URL = "/api/v1/notice"; + +class NoticeAPI { + static getPage(queryParams: NoticePageQuery) { + return request>({ + url: `${NOTICE_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + + static save(data: WebNotice) { + return request({ + url: `${NOTICE_BASE_URL}/save`, + method: "put", + data: data, + }); + } + + static setByIds(ids: string, state: number) { + return request({ + url: `${NOTICE_BASE_URL}/${ids}`, + method: "put", + params: { state: state }, + }); + } +} + +export default NoticeAPI; + +export interface NoticePageQuery extends PageQuery { + state?: number; +} + +export interface WebNotice { + noticeId?: number; + + title?: string; + content?: string; + + state?: number; + noticeType?: number; + + releaseTime?: number; + shieldTime?: number; +} diff --git a/src/api/order.ts b/src/api/order.ts new file mode 100644 index 0000000..65482e4 --- /dev/null +++ b/src/api/order.ts @@ -0,0 +1,37 @@ +import request from "@/utils/request"; + +const ORDER_BASE_URL = "/api/v1/order"; + +class OrderAPI { + static listPage(query: OrderPageQuery) { + return request>({ + url: `${ORDER_BASE_URL}/page`, + method: "get", + params: query, + }); + } +} + +export default OrderAPI; + +export interface OrderPageVO { + orderId: number; + userId: number; + userName: string; + nickName?: string; + money: number; + orderNumber: string; + payway: string; + state: number; + paytime: number; + endtime?: number; + remark?: string; +} + +export interface OrderPageQuery extends PageQuery { + userId?: number; + state?: number; + payway?: string; + /** 添加时间 */ + paytime?: [number, number]; +} diff --git a/src/api/role.ts b/src/api/role.ts new file mode 100644 index 0000000..e33a502 --- /dev/null +++ b/src/api/role.ts @@ -0,0 +1,139 @@ +import request from "@/utils/request"; + +const ROLE_BASE_URL = "/api/v1/roles"; + +class RoleAPI { + /** 获取角色分页数据 */ + static getPage(queryParams?: RolePageQuery) { + return request>({ + url: `${ROLE_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + + /** 获取角色下拉数据源 */ + static getOptions() { + return request({ + url: `${ROLE_BASE_URL}/options`, + method: "get", + }); + } + + /** + * 获取角色的菜单ID集合 + * + * @param roleId 角色ID + * @returns 角色的菜单ID集合 + */ + static getRoleMenuIds(roleId: number) { + return request({ + url: `${ROLE_BASE_URL}/${roleId}/menuIds`, + method: "get", + }); + } + + /** + * 分配菜单权限 + * + * @param roleId 角色ID + * @param data 菜单ID集合 + */ + static updateRoleMenus(roleId: number, data: number[]) { + return request({ + url: `${ROLE_BASE_URL}/${roleId}/menus`, + method: "put", + data: data, + }); + } + + /** + * 获取角色表单数据 + * + * @param id 角色ID + * @returns 角色表单数据 + */ + static getFormData(id: number) { + return request({ + url: `${ROLE_BASE_URL}/${id}/form`, + method: "get", + }); + } + + /** 添加角色 */ + static add(data: RoleForm) { + return request({ + url: `${ROLE_BASE_URL}`, + method: "post", + data: data, + }); + } + + /** + * 更新角色 + * + * @param id 角色ID + * @param data 角色表单数据 + */ + static update(id: number, data: RoleForm) { + return request({ + url: `${ROLE_BASE_URL}/${id}`, + method: "put", + data: data, + }); + } + + /** + * 批量删除角色,多个以英文逗号(,)分割 + * + * @param ids 角色ID字符串,多个以英文逗号(,)分割 + */ + static deleteByIds(ids: string) { + return request({ + url: `${ROLE_BASE_URL}/${ids}`, + method: "delete", + }); + } +} + +export default RoleAPI; + +/** 角色分页查询参数 */ +export interface RolePageQuery extends PageQuery { + /** 搜索关键字 */ + keywords?: string; +} + +/** 角色分页对象 */ +export interface RolePageVO { + /** 角色编码 */ + code?: string; + /** 角色ID */ + id?: number; + /** 角色名称 */ + name?: string; + /** 排序 */ + sort?: number; + /** 角色状态 */ + status?: number; + /** 创建时间 */ + createTime?: Date; + /** 修改时间 */ + updateTime?: Date; +} + +/** 角色表单对象 */ +export interface RoleForm { + /** 角色ID */ + id?: number; + /** 角色编码 */ + code: string; + /** 数据权限 */ + dataScope?: number; + /** 角色名称 */ + name: string; + /** 排序 */ + sort?: number; + /** 角色状态(1-正常;0-停用) */ + status?: number; +} diff --git a/src/api/software.ts b/src/api/software.ts new file mode 100644 index 0000000..688cc03 --- /dev/null +++ b/src/api/software.ts @@ -0,0 +1,130 @@ +import request from "@/utils/request"; + +const SOFTWARE_BASE_URL = "/api/v1/software"; + +class SoftwareAPI { + static listSoftware() { + return request({ + url: `${SOFTWARE_BASE_URL}/list`, + method: "get", + }); + } + + static listFont() { + return request({ + url: `${SOFTWARE_BASE_URL}/listFont`, + method: "get", + }); + } + + static getVersion(id: number, os: number) { + return request({ + url: `${SOFTWARE_BASE_URL}/lastVersion/${id}/${os}`, + method: "get", + }); + } + + static page(query: VersionPageQuery) { + return request>({ + url: `${SOFTWARE_BASE_URL}/page`, + method: "get", + params: query, + }); + } + + static update(data: FullVersion) { + return request({ + url: `${SOFTWARE_BASE_URL}/update`, + method: "put", + data: data, + }); + } + + static maxVersion(swId: number, innerId: number) { + return request({ + url: `${SOFTWARE_BASE_URL}/maxVersion`, + method: "get", + params: { softwareId: swId, innerId: innerId }, + }); + } + + static swDownload(swId: number, os: number) { + return request({ + url: `${SOFTWARE_BASE_URL}/swDownload`, + method: "put", + params: { softwareId: swId, os: os }, + }); + } + + static setByIds(ids: string, state: number) { + return request({ + url: `${SOFTWARE_BASE_URL}/${ids}`, + method: "put", + params: { state: state }, + }); + } +} + +export default SoftwareAPI; + +/** 软件信息 */ +export interface SoftwareInfo { + /** 软件ID */ + softwareId: number; + /** 软件名称 */ + name: string; + /** 适用系统 */ + os: number; +} + +/** 版本信息 */ +export interface VersionInfo { + softwareId: number; + os: number; + version: string; + installPath: string; + releaseDate: number; + packageSize: number; + describe?: string; + pictures?: string[]; +} + +/** 音色库信息 */ +export interface FontInfo { + fontsId: number; + name: string; + path: string; + size: number; + describe?: string; +} + +export interface VersionPageQuery extends PageQuery { + softwareId?: number; + innerId?: number; + incompatible?: number; +} + +export interface FullVersion { + versionId?: number; + softwareId?: number; + innerId?: number; + versionMajor?: number; + versionMinor?: number; + versionModify?: number; + versionBuild?: number; + incompatible?: number; + installSize?: number; + installPath?: string; + releaseTime?: number; + describeText?: string; + accessNum?: number; + downloadNum?: number; + isDeleted?: number; +} + +export interface VersionNum { + major: number; + minor: number; + modify: number; + build: number; +} diff --git a/src/api/statistics.ts b/src/api/statistics.ts new file mode 100644 index 0000000..a428d83 --- /dev/null +++ b/src/api/statistics.ts @@ -0,0 +1,48 @@ +import request from "@/utils/request"; + +const STATISTICS_BASE_URL = "/api/v1/statistics"; + +class StatisticsAPI { + static page(query: StatisticsPageQuery) { + return request>({ + url: `${STATISTICS_BASE_URL}/page`, + method: "get", + params: query, + }); + } +} + +export default StatisticsAPI; + +export interface StatisticsBO { + statisticsId?: number; + statisticsDate?: number; //统计日期 + login?: string; //登录次数 + loginUsersum?: string; //登录人数 + register?: string; //注册人数 + mofangAccess?: string; //软件使用次数 + downloadNum?: string; //软件下载次数 + accompanyUse?: string; //风格使用次数 + accompanyConsume?: string; //风格消费额 + recharge?: string; //音符充值 + consume?: string; //音符消费 +} + +export interface StatisticsVO { + statisticsId?: number; + statisticsDate?: number; //统计日期 + login?: number[]; //登录次数 + loginUsersum?: number[]; //登录人数 + register?: number[]; //注册人数 + mofangAccess?: number[]; //软件使用次数 + downloadNum?: number[]; //软件下载次数 + accompanyUse?: number[]; //风格使用次数 + accompanyConsume?: number[]; //风格消费总额 + recharge?: number[]; //音符充值 + consume?: number[]; //音符消费 +} + +export interface StatisticsPageQuery extends PageQuery { + /** 时间范围 */ + range?: [number, number]; +} diff --git a/src/api/style.ts b/src/api/style.ts new file mode 100644 index 0000000..5285514 --- /dev/null +++ b/src/api/style.ts @@ -0,0 +1,68 @@ +import request from "@/utils/request"; + +const STYLE_BASE_URL = "/api/v1/style"; + +class StyleAPI { + static statistic() { + return request({ + url: `${STYLE_BASE_URL}/statistic`, + method: "get", + }); + } + + static page(query: StylePageQuery) { + return request>({ + url: `${STYLE_BASE_URL}/page`, + method: "get", + params: query, + }); + } + + static update(data: StyleUpdate) { + return request({ + url: `${STYLE_BASE_URL}/update`, + method: "put", + data: data, + }); + } +} + +export default StyleAPI; + +export interface StyleStatistic { + ts0: number; + ts1: number; + ts2: number; + ts3: number; + temp: number; +} + +export interface StyleVO { + accompanyId: number; + + cost: number; //使用价格 + score: number; //评分 + state: number; //状态 + name: string; //风格名称 + description?: string; //风格描述 + author: number; //作者ID + authorName?: string; //作者昵称 + timesignature: number; //节拍(0:4/4;1:3/4;2:2/4;3:6/8) + tempo: number; //速度 + changeTime: number; //编辑时间 + scoreTime: number; //评分时间 + useCount: number; //使用次数 + sellCount: number; //购买次数 +} + +export interface StylePageQuery extends PageQuery { + author?: number; + ts?: number; + state?: number; +} + +export interface StyleUpdate { + accompanyId?: number; + cost?: number; //使用价格 + state?: number; //状态 +} diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100644 index 0000000..93af898 --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,612 @@ +import request from "@/utils/request"; +import { SendCodeData } from "./auth"; + +const USER_BASE_URL = "/api/v1/users"; + +class UserAPI { + /** + * 获取当前登录用户信息 + * + * @returns 登录用户昵称、头像信息,包括角色和权限 + */ + static getInfo() { + return request({ + url: `${USER_BASE_URL}/me`, + method: "get", + }); + } + + /** + * 获取用户分页列表 + * + * @param queryParams 查询参数 + */ + static getPage(queryParams: UserPageQuery) { + return request>({ + url: `${USER_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + + /** + * 获取用户表单详情 + * + * @param userId 用户ID + * @returns 用户表单详情 + */ + static getFormData(userId: number) { + return request({ + url: `${USER_BASE_URL}/${userId}/form`, + method: "get", + }); + } + + /** + * 添加用户 + * + * @param data 用户表单数据 + */ + static add(data: UserForm) { + return request({ + url: `${USER_BASE_URL}`, + method: "post", + data: data, + }); + } + + /** + * 修改用户 + * + * @param id 用户ID + * @param data 用户表单数据 + */ + static update(id: number, data: UserForm) { + return request({ + url: `${USER_BASE_URL}/${id}`, + method: "put", + data: data, + }); + } + + // /** + // * 修改用户密码 + // * + // * @param id 用户ID + // * @param password 新密码 + // */ + // static resetPassword(id: number, password: string) { + // return request({ + // url: `${USER_BASE_URL}/${id}/password/reset`, + // method: "put", + // params: { password: password }, + // }); + // } + + /** + * 批量删除用户,多个以英文逗号(,)分割 + * + * @param ids 用户ID字符串,多个以英文逗号(,)分割 + */ + static deleteByIds(ids: string) { + return request({ + url: `${USER_BASE_URL}/${ids}`, + method: "delete", + }); + } + + /** + * 批量修改用户状态,多个以英文逗号(,)分割 + * + * @param ids 用户ID字符串,多个以英文逗号(,)分割 + */ + static setStateByIds(ids: string, state: number) { + return request({ + url: `${USER_BASE_URL}/${ids}/${state}`, + method: "patch", + }); + } + + /** 下载用户导入模板 */ + static downloadTemplate() { + return request({ + url: `${USER_BASE_URL}/template`, + method: "get", + responseType: "arraybuffer", + }); + } + + /** + * 导出用户 + * + * @param queryParams 查询参数 + */ + static export(queryParams: UserPageQuery) { + return request({ + url: `${USER_BASE_URL}/export`, + method: "get", + params: queryParams, + responseType: "arraybuffer", + }); + } + + /** + * 导入用户 + * + * @param deptId 部门ID + * @param file 导入文件 + */ + static import(deptId: number, file: File) { + const formData = new FormData(); + formData.append("file", file); + return request({ + url: `${USER_BASE_URL}/import`, + method: "post", + params: { deptId: deptId }, + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + /** 获取个人中心用户信息 */ + static getProfile() { + return request({ + url: `${USER_BASE_URL}/profile`, + method: "get", + }); + } + + /** 修改个人中心用户信息 */ + static updateProfile(data: UserProfileForm) { + return request({ + url: `${USER_BASE_URL}/profile`, + method: "put", + data: data, + }); + } + + /** 修改个人中心用户密码 */ + static changePassword(data: PasswordChangeForm) { + return request({ + url: `${USER_BASE_URL}/password`, + method: "put", + data: data, + }); + } + + /** + * 发送手机/邮箱验证码 + * + * @param contact 联系方式 手机号/邮箱 + */ + static sendBindCode(data: SendCodeData) { + const formData = new FormData(); + formData.append("contact", data.contact); + formData.append("captchaKey", data.captchaKey); + formData.append("captchaCode", data.captchaCode); + + return request({ + url: `${USER_BASE_URL}/sendBindCode`, + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + /** 绑定个人中心用户手机 */ + static bindMobile(data: MobileBindingForm) { + return request({ + url: `${USER_BASE_URL}/mobile`, + method: "put", + data: data, + }); + } + + /** 绑定个人中心用户邮箱 */ + static bindEmail(data: EmailBindingForm) { + return request({ + url: `${USER_BASE_URL}/email`, + method: "put", + data: data, + }); + } + + /** 获取账户信息 */ + static getAccountInfo() { + return request({ + url: `${USER_BASE_URL}/account`, + method: "get", + }); + } + + /** + * 修改用户昵称 + * + * @param id 用户ID + * @param nickname 用户昵称 + */ + static updateNickname(id: number, nickname: string) { + return request({ + url: `${USER_BASE_URL}/${id}/nickname`, + method: "patch", + params: { nickname: nickname }, + }); + } + + /** + * 修改用户手机 + * + * @param id 用户ID + * @param phone 用户手机号 + */ + static updatePhone(id: number, phone: string) { + return request({ + url: `${USER_BASE_URL}/${id}/phone`, + method: "patch", + params: { phone: phone }, + }); + } + + /** 修改个人中心用户密码 */ + static resetPassword(data: ResetPasswordForm) { + return request({ + url: `${USER_BASE_URL}/resetPassword`, + method: "post", + data: data, + }); + } + + /** 获取用户实名认证信息 */ + static getRealNameInfo() { + return request({ + url: `${USER_BASE_URL}/realname`, + method: "get", + }); + } + + /** 更新用户实名认证信息 */ + static updateRealNameInfo(data: RealNameInfo) { + return request({ + url: `${USER_BASE_URL}/realname`, + method: "put", + data: data, + }); + } + + static getRealNamePage(queryParams: PageQuery) { + return request>({ + url: `${USER_BASE_URL}/realname/page`, + method: "get", + params: queryParams, + }); + } + + static verifyRealName(userId: number, reason: string) { + return request({ + url: `${USER_BASE_URL}/${userId}/realname`, + method: "post", + params: { reason: reason }, + }); + } + + static beingVIP(vipType: number) { + return request({ + url: `${USER_BASE_URL}/vip`, + method: "post", + params: { vipType: vipType }, + }); + } + + static buyNotes(payType: string, amount: string) { + return request({ + url: `${USER_BASE_URL}/buyNotes`, + method: "post", + params: { payType: payType, amount: amount }, + }); + } + + static checkOrder(order: string) { + return request({ + url: `${USER_BASE_URL}/checkOrder`, + method: "get", + params: { order: order }, + }); + } + + static draw(data: DrawForm) { + return request({ + url: `${USER_BASE_URL}/draw`, + method: "post", + data: data, + }); + } + + static drawPage(queryParams: PageQuery) { + return request>({ + url: `${USER_BASE_URL}/draw/page`, + method: "get", + params: queryParams, + }); + } + + static verifyDraw(orderId: number, reason: string) { + return request({ + url: `${USER_BASE_URL}/draw`, + method: "put", + params: { orderId: orderId, reason: reason }, + }); + } +} + +export default UserAPI; + +/** 登录用户信息 */ +export interface UserInfo { + /** 用户ID */ + userId?: number; + + /** 用户名 */ + username?: string; + + /** 昵称 */ + nickname?: string; + + /** 头像URL */ + avatar?: string; + + /** 角色 */ + roles: string[]; + + /** 权限 */ + perms: string[]; +} + +/** + * 用户分页查询对象 + */ +export interface UserPageQuery extends PageQuery { + /** 搜索关键字 */ + keywords?: string; + + /** 用户状态 */ + status?: number; + + /** 部门ID */ + // deptId?: number; + + /** 注册时间 */ + regTime?: [number, number]; +} + +/** 用户分页对象 */ +export interface UserPageVO { + /** 用户头像URL */ + avatar?: string; + /** 创建时间 */ + regTime?: number; + /** 部门名称 */ + // deptName?: string; + /** 用户邮箱 */ + email?: string; + /** 性别 */ + genderLabel?: string; + /** 用户ID */ + id?: number; + /** 手机号 */ + mobile?: string; + /** 用户昵称 */ + nickname?: string; + /** 角色名称,多个使用英文逗号(,)分割 */ + // roleNames?: string; + /** 用户状态(1:启用;0:禁用) */ + status?: number; + /** 用户名 */ + username?: string; + + name?: string; + isName?: number; + isPhone?: number; + loginTime?: number; + loginNum?: number; + deleteTime?: number; + vipTime?: number; + integral?: number; + authority?: number; +} + +/** 用户表单类型 */ +export interface UserForm { + /** 用户头像 */ + avatar?: string; + /** 部门ID */ + // deptId?: number; + /** 邮箱 */ + email?: string; + /** 性别 */ + gender?: number; + /** 用户ID */ + id?: number; + /** 手机号 */ + mobile?: string; + /** 昵称 */ + nickname?: string; + /** 角色ID集合 */ + roleIds?: number[]; + /** 用户状态(1:正常;0:禁用) */ + status?: number; + /** 用户名 */ + username?: string; +} + +/** 个人中心用户信息 */ +export interface UserProfileVO { + /** 用户ID */ + id?: number; + + /** 用户名 */ + username?: string; + + /** 昵称 */ + nickname?: string; + + /** 头像URL */ + avatar?: string; + + /** 性别 */ + gender?: number; + + /** 手机号 */ + mobile?: string; + + /** 邮箱 */ + email?: string; + + // /** 部门名称 */ + // deptName?: string; + + /** 角色名称,多个使用英文逗号(,)分割 */ + roleNames?: string; + + /** 创建时间 */ + createTime?: Date; +} + +/** 个人中心用户信息表单 */ +export interface UserProfileForm { + /** 用户ID */ + id?: number; + + /** 用户名 */ + username?: string; + + /** 昵称 */ + nickname?: string; + + /** 头像URL */ + avatar?: string; + + /** 性别 */ + gender?: number; + + /** 手机号 */ + mobile?: string; + + /** 邮箱 */ + email?: string; +} + +/** 修改密码表单 */ +export interface PasswordChangeForm { + /** 原密码 */ + oldPassword?: string; + /** 新密码 */ + newPassword?: string; + /** 确认新密码 */ + confirmPassword?: string; +} + +/** 重置密码表单 */ +export interface ResetPasswordForm { + /** 用户ID */ + userId?: number; + /** 用户名 */ + username?: string; + /** 手机或邮箱 */ + contact?: string; + /** 验证码 */ + verifyCode?: string; + /** 密码 */ + password?: string; +} + +/** 修改手机表单 */ +export interface MobileBindingForm { + /** 手机号 */ + mobile?: string; + /** 验证码 */ + code?: string; +} + +/** 修改邮箱表单 */ +export interface EmailBindingForm { + /** 邮箱 */ + email?: string; + /** 验证码 */ + code?: string; +} + +/** 账户信息 */ +export interface AccountInfo { + /** 用户ID */ + userId?: number; + /** 账户状态(0:注销;1:锁定;2:已激活;3:未激活;4:在线;5:旧版用户) */ + state?: number; + + /** 真实名称 */ + name?: string; + /** 是否实名认证(1:是;2:不是;3:正在认证) */ + isName?: number; + + /** 手机号 */ + phone?: string; + /** 是否手机认证(1:是;2:不是) */ + isPhone?: number; + + /** 邮箱 */ + email?: string; + /** 邮箱激活时间 */ + // activateTime?: number; + + /** 唱作音符数 */ + integral?: number; + + /** VIP过期时间(-1:终身, -2: 已过期, 0: 非会员) */ + vipTime?: number; + /** VIP状态 */ + // vipState?: number; + + /** 注册时间 */ + regTime?: number; +} + +export interface RealNameInfo { + /** 用户ID */ + userId?: number; + + /** 真实名称 */ + name?: string; + isName?: number; + + identityType?: number; //证件类型 + identityCard?: string; //证件号 + identityPic?: string; //证件照片 + address?: string; //联系地址 +} + +export interface RealNameInfoPageQuery extends PageQuery { + isName?: number; +} + +export interface DrawForm { + amount: number; + cardNum: string; + bankName: string; + cardName?: string; +} + +export interface DrawPageVO { + orderId: number; + userId: number; + userName: string; + money: string; + bankName: string; + cardNum: string; + cardName?: string; + takeTime: number; + makeTime?: number; + state: number; +} diff --git a/src/assets/icons/alipay-l.svg b/src/assets/icons/alipay-l.svg new file mode 100644 index 0000000..fa2515a --- /dev/null +++ b/src/assets/icons/alipay-l.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/alipay-s.svg b/src/assets/icons/alipay-s.svg new file mode 100644 index 0000000..433f98d --- /dev/null +++ b/src/assets/icons/alipay-s.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/api.svg b/src/assets/icons/api.svg new file mode 100644 index 0000000..0181bdd --- /dev/null +++ b/src/assets/icons/api.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/backtop.svg b/src/assets/icons/backtop.svg new file mode 100644 index 0000000..f8e6aa0 --- /dev/null +++ b/src/assets/icons/backtop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/captcha.svg b/src/assets/icons/captcha.svg new file mode 100644 index 0000000..8b1da30 --- /dev/null +++ b/src/assets/icons/captcha.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/cascader.svg b/src/assets/icons/cascader.svg new file mode 100644 index 0000000..57209bf --- /dev/null +++ b/src/assets/icons/cascader.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..7373b3d --- /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..e99c978 --- /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..2005198 --- /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..fc5cf71 --- /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..27ffc32 --- /dev/null +++ b/src/assets/icons/close_other.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/close_right.svg b/src/assets/icons/close_right.svg new file mode 100644 index 0000000..b96dc1c --- /dev/null +++ b/src/assets/icons/close_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/code.svg b/src/assets/icons/code.svg new file mode 100644 index 0000000..d8b546c --- /dev/null +++ b/src/assets/icons/code.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/collapse.svg b/src/assets/icons/collapse.svg new file mode 100644 index 0000000..1507568 --- /dev/null +++ b/src/assets/icons/collapse.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..db60220 --- /dev/null +++ b/src/assets/icons/dict.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/document.svg b/src/assets/icons/document.svg new file mode 100644 index 0000000..aaa0574 --- /dev/null +++ b/src/assets/icons/document.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/download.svg b/src/assets/icons/download.svg new file mode 100644 index 0000000..a8077dc --- /dev/null +++ b/src/assets/icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/file.svg b/src/assets/icons/file.svg new file mode 100644 index 0000000..fac9bf0 --- /dev/null +++ b/src/assets/icons/file.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/finish-black.svg b/src/assets/icons/finish-black.svg new file mode 100644 index 0000000..53e6fe4 --- /dev/null +++ b/src/assets/icons/finish-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/finish-white.svg b/src/assets/icons/finish-white.svg new file mode 100644 index 0000000..f9f87f0 --- /dev/null +++ b/src/assets/icons/finish-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/fullscreen-exit.svg b/src/assets/icons/fullscreen-exit.svg new file mode 100644 index 0000000..2452f2b --- /dev/null +++ b/src/assets/icons/fullscreen-exit.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..4b6ee11 --- /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..1adfa4e --- /dev/null +++ b/src/assets/icons/github.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..1e1feab --- /dev/null +++ b/src/assets/icons/homepage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/ip.svg b/src/assets/icons/ip.svg new file mode 100644 index 0000000..087f3bb --- /dev/null +++ b/src/assets/icons/ip.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/java.svg b/src/assets/icons/java.svg new file mode 100644 index 0000000..eaa93db --- /dev/null +++ b/src/assets/icons/java.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/language.svg b/src/assets/icons/language.svg new file mode 100644 index 0000000..e754062 --- /dev/null +++ b/src/assets/icons/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/menu.svg b/src/assets/icons/menu.svg new file mode 100644 index 0000000..f5875d3 --- /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..deacdc3 --- /dev/null +++ b/src/assets/icons/message.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/monitor.svg b/src/assets/icons/monitor.svg new file mode 100644 index 0000000..f153b9c --- /dev/null +++ b/src/assets/icons/monitor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/project.svg b/src/assets/icons/project.svg new file mode 100644 index 0000000..eaf6a12 --- /dev/null +++ b/src/assets/icons/project.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/pv.svg b/src/assets/icons/pv.svg new file mode 100644 index 0000000..cd7da06 --- /dev/null +++ b/src/assets/icons/pv.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/qrcode-black.svg b/src/assets/icons/qrcode-black.svg new file mode 100644 index 0000000..0bcd90e --- /dev/null +++ b/src/assets/icons/qrcode-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/qrcode-white.svg b/src/assets/icons/qrcode-white.svg new file mode 100644 index 0000000..d73dcb0 --- /dev/null +++ b/src/assets/icons/qrcode-white.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..e598ed1 --- /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..5d25278 --- /dev/null +++ b/src/assets/icons/role.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/scan-black.svg b/src/assets/icons/scan-black.svg new file mode 100644 index 0000000..339a285 --- /dev/null +++ b/src/assets/icons/scan-black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/scan-white.svg b/src/assets/icons/scan-white.svg new file mode 100644 index 0000000..6fb00c0 --- /dev/null +++ b/src/assets/icons/scan-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/setting.svg b/src/assets/icons/setting.svg new file mode 100644 index 0000000..fbc4945 --- /dev/null +++ b/src/assets/icons/setting.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..f92f852 --- /dev/null +++ b/src/assets/icons/size.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..2e6045b --- /dev/null +++ b/src/assets/icons/system.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/table.svg b/src/assets/icons/table.svg new file mode 100644 index 0000000..1a16abb --- /dev/null +++ b/src/assets/icons/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/todo.svg b/src/assets/icons/todo.svg new file mode 100644 index 0000000..f48e667 --- /dev/null +++ b/src/assets/icons/todo.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/tree.svg b/src/assets/icons/tree.svg new file mode 100644 index 0000000..51aea8f --- /dev/null +++ b/src/assets/icons/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/typescript.svg b/src/assets/icons/typescript.svg new file mode 100644 index 0000000..781d6f8 --- /dev/null +++ b/src/assets/icons/typescript.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/unipay.svg b/src/assets/icons/unipay.svg new file mode 100644 index 0000000..431a227 --- /dev/null +++ b/src/assets/icons/unipay.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..8e693ec --- /dev/null +++ b/src/assets/icons/user.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/uv.svg b/src/assets/icons/uv.svg new file mode 100644 index 0000000..c5be7eb --- /dev/null +++ b/src/assets/icons/uv.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/vue.svg b/src/assets/icons/vue.svg new file mode 100644 index 0000000..456f876 --- /dev/null +++ b/src/assets/icons/vue.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/wxpay-l.svg b/src/assets/icons/wxpay-l.svg new file mode 100644 index 0000000..be425bd --- /dev/null +++ b/src/assets/icons/wxpay-l.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/wxpay-s.svg b/src/assets/icons/wxpay-s.svg new file mode 100644 index 0000000..c54b63d --- /dev/null +++ b/src/assets/icons/wxpay-s.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/xml.svg b/src/assets/icons/xml.svg new file mode 100644 index 0000000..f041213 --- /dev/null +++ b/src/assets/icons/xml.svg @@ -0,0 +1 @@ + diff --git a/src/assets/images/401.gif b/src/assets/images/401.gif new file mode 100644 index 0000000..4c930e7 Binary files /dev/null and b/src/assets/images/401.gif differ diff --git a/src/assets/images/404.png b/src/assets/images/404.png new file mode 100644 index 0000000..4e09829 Binary files /dev/null and b/src/assets/images/404.png differ diff --git a/src/assets/images/404_cloud.png b/src/assets/images/404_cloud.png new file mode 100644 index 0000000..a81d8da Binary files /dev/null and b/src/assets/images/404_cloud.png differ diff --git a/src/assets/images/Software/ac_one_blur.png b/src/assets/images/Software/ac_one_blur.png new file mode 100644 index 0000000..fd8f0b3 Binary files /dev/null and b/src/assets/images/Software/ac_one_blur.png differ diff --git a/src/assets/images/Software/ac_one_focus.png b/src/assets/images/Software/ac_one_focus.png new file mode 100644 index 0000000..7cd54ed Binary files /dev/null and b/src/assets/images/Software/ac_one_focus.png differ diff --git a/src/assets/images/Software/android_qr.png b/src/assets/images/Software/android_qr.png new file mode 100644 index 0000000..bef917d Binary files /dev/null and b/src/assets/images/Software/android_qr.png differ diff --git a/src/assets/images/Software/icon_android_blur.png b/src/assets/images/Software/icon_android_blur.png new file mode 100644 index 0000000..660f83a Binary files /dev/null and b/src/assets/images/Software/icon_android_blur.png differ diff --git a/src/assets/images/Software/icon_android_focus.png b/src/assets/images/Software/icon_android_focus.png new file mode 100644 index 0000000..26b70f5 Binary files /dev/null and b/src/assets/images/Software/icon_android_focus.png differ diff --git a/src/assets/images/Software/icon_download.png b/src/assets/images/Software/icon_download.png new file mode 100644 index 0000000..84af2ba Binary files /dev/null and b/src/assets/images/Software/icon_download.png differ diff --git a/src/assets/images/Software/icon_font.png b/src/assets/images/Software/icon_font.png new file mode 100644 index 0000000..0c434c1 Binary files /dev/null and b/src/assets/images/Software/icon_font.png differ diff --git a/src/assets/images/Software/icon_ios_blur.png b/src/assets/images/Software/icon_ios_blur.png new file mode 100644 index 0000000..6aebb06 Binary files /dev/null and b/src/assets/images/Software/icon_ios_blur.png differ diff --git a/src/assets/images/Software/icon_ios_focus.png b/src/assets/images/Software/icon_ios_focus.png new file mode 100644 index 0000000..a8704bf Binary files /dev/null and b/src/assets/images/Software/icon_ios_focus.png differ diff --git a/src/assets/images/Software/icon_pc_blur.png b/src/assets/images/Software/icon_pc_blur.png new file mode 100644 index 0000000..471c4fb Binary files /dev/null and b/src/assets/images/Software/icon_pc_blur.png differ diff --git a/src/assets/images/Software/icon_pc_focus.png b/src/assets/images/Software/icon_pc_focus.png new file mode 100644 index 0000000..53147f0 Binary files /dev/null and b/src/assets/images/Software/icon_pc_focus.png differ diff --git a/src/assets/images/Software/ios_qr2.png b/src/assets/images/Software/ios_qr2.png new file mode 100644 index 0000000..94be98c Binary files /dev/null and b/src/assets/images/Software/ios_qr2.png differ diff --git a/src/assets/images/default.jpg b/src/assets/images/default.jpg new file mode 100644 index 0000000..58f99f0 Binary files /dev/null and b/src/assets/images/default.jpg differ diff --git a/src/assets/images/flower.png b/src/assets/images/flower.png new file mode 100644 index 0000000..4963d78 Binary files /dev/null and b/src/assets/images/flower.png differ diff --git a/src/assets/images/hot_bank.jpg b/src/assets/images/hot_bank.jpg new file mode 100644 index 0000000..dd266b9 Binary files /dev/null and b/src/assets/images/hot_bank.jpg differ diff --git a/src/assets/images/login-background-dark.jpg b/src/assets/images/login-background-dark.jpg new file mode 100644 index 0000000..50dc817 Binary files /dev/null and b/src/assets/images/login-background-dark.jpg differ diff --git a/src/assets/images/login-background-light.jpg b/src/assets/images/login-background-light.jpg new file mode 100644 index 0000000..993b958 Binary files /dev/null and b/src/assets/images/login-background-light.jpg differ diff --git a/src/assets/images/sad.png b/src/assets/images/sad.png new file mode 100644 index 0000000..e3c6e40 Binary files /dev/null and b/src/assets/images/sad.png differ diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..08f4e8d Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo_s.png b/src/assets/logo_s.png new file mode 100644 index 0000000..b3cd746 Binary files /dev/null and b/src/assets/logo_s.png differ diff --git a/src/assets/web_logo.png b/src/assets/web_logo.png new file mode 100644 index 0000000..b439754 Binary files /dev/null and b/src/assets/web_logo.png differ diff --git a/src/components/AmountInput/index.vue b/src/components/AmountInput/index.vue new file mode 100644 index 0000000..bf0a75b --- /dev/null +++ b/src/components/AmountInput/index.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/components/AppLink/index.vue b/src/components/AppLink/index.vue new file mode 100644 index 0000000..43bbb30 --- /dev/null +++ b/src/components/AppLink/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..ea2b1af --- /dev/null +++ b/src/components/Breadcrumb/index.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/components/CURD/PageContent.vue b/src/components/CURD/PageContent.vue new file mode 100644 index 0000000..d36167e --- /dev/null +++ b/src/components/CURD/PageContent.vue @@ -0,0 +1,1023 @@ + + + + + diff --git a/src/components/CURD/PageForm.vue b/src/components/CURD/PageForm.vue new file mode 100644 index 0000000..0979019 --- /dev/null +++ b/src/components/CURD/PageForm.vue @@ -0,0 +1,174 @@ + + + diff --git a/src/components/CURD/PageModal.vue b/src/components/CURD/PageModal.vue new file mode 100644 index 0000000..214b28e --- /dev/null +++ b/src/components/CURD/PageModal.vue @@ -0,0 +1,425 @@ + + + + + diff --git a/src/components/CURD/PageSearch.vue b/src/components/CURD/PageSearch.vue new file mode 100644 index 0000000..f2baf1d --- /dev/null +++ b/src/components/CURD/PageSearch.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/src/components/CURD/types.ts b/src/components/CURD/types.ts new file mode 100644 index 0000000..229dc0b --- /dev/null +++ b/src/components/CURD/types.ts @@ -0,0 +1,272 @@ +import type { + DialogProps, + DrawerProps, + FormItemRule, + FormProps, + PaginationProps, + TableProps, + ColProps, +} from "element-plus"; +import PageContent from "./PageContent.vue"; +import PageForm from "./PageForm.vue"; +import PageModal from "./PageModal.vue"; +import PageSearch from "./PageSearch.vue"; + +export type PageSearchInstance = InstanceType; +export type PageContentInstance = InstanceType; +export type PageModalInstance = InstanceType; +export type PageFormInstance = InstanceType; + +export type IObject = Record; + +export interface IOperatData { + name: string; + row: IObject; + column: IObject; + $index: number; +} + +export interface ISearchConfig { + // 页面名称(参与组成权限标识,如sys:user:xxx) + pageName: string; + // 表单项 + formItems: Array<{ + // 组件类型(如input,select等) + type?: "input" | "select" | "tree-select" | "date-picker" | "input-tag"; + // 标签文本 + label: string; + // 标签提示 + tips?: string; + // 键名 + prop: string; + // 组件属性(input-tag组件支持join,btnText,size属性) + attrs?: IObject; + // 初始值 + initialValue?: any; + // 可选项(适用于select组件) + options?: { label: string; value: any }[]; + // 初始化数据函数扩展 + initFn?: (formItem: IObject) => void; + }>; + // 是否开启展开和收缩 + isExpandable?: boolean; + // 默认展示的表单项数量 + showNumber?: number; +} + +export interface IContentConfig { + // 页面名称(参与组成权限标识,如sys:user:xxx) + pageName: string; + // table组件属性 + table?: Omit, "data">; + // pagination组件属性 + pagination?: + | boolean + | Partial< + Omit< + PaginationProps, + "v-model:page-size" | "v-model:current-page" | "total" | "currentPage" + > + >; + // 列表的网络请求函数(需返回promise) + indexAction: (queryParams: T) => Promise; + // 默认的分页相关的请求参数 + request?: { + pageName: string; + limitName: string; + }; + // 数据格式解析的回调函数 + parseData?: (res: any) => { + total: number; + list: IObject[]; + [key: string]: any; + }; + // 修改属性的网络请求函数(需返回promise) + modifyAction?: (data: { + [key: string]: any; + field: string; + value: boolean | string | number; + }) => Promise; + // 删除的网络请求函数(需返回promise) + deleteAction?: (ids: string) => Promise; + // 后端导出的网络请求函数(需返回promise) + exportAction?: (queryParams: T) => Promise; + // 前端全量导出的网络请求函数(需返回promise) + exportsAction?: (queryParams: T) => Promise; + // 导入模板 + importTemplate?: string | (() => Promise); + // 后端导入的网络请求函数(需返回promise) + importAction?: (file: File) => Promise; + // 前端导入的网络请求函数(需返回promise) + importsAction?: (data: IObject[]) => Promise; + // 主键名(默认为id) + pk?: string; + // 表格工具栏(默认支持add,delete,export,也可自定义) + toolbar?: Array< + | "add" + | "delete" + | "import" + | "export" + | { + auth: string; + icon?: string; + name: string; + text: string; + type?: "primary" | "success" | "warning" | "danger" | "info"; + } + >; + // 表格工具栏右侧图标 + defaultToolbar?: Array< + | "refresh" + | "filter" + | "imports" + | "exports" + | "search" + | { + name: string; + icon: string; + title?: string; + auth?: string; + } + >; + // table组件列属性(额外的属性templet,operat,slotName) + cols: Array<{ + type?: "default" | "selection" | "index" | "expand"; + label?: string; + prop?: string; + width?: string | number; + align?: "left" | "center" | "right"; + columnKey?: string; + reserveSelection?: boolean; + // 列是否显示 + show?: boolean; + // 模板 + templet?: + | "image" + | "list" + | "url" + | "switch" + | "input" + | "price" + | "percent" + | "icon" + | "date" + | "tool" + | "custom"; + // image模板相关参数 + imageWidth?: number; + imageHeight?: number; + // list模板相关参数 + selectList?: IObject; + // switch模板相关参数 + activeValue?: boolean | string | number; + inactiveValue?: boolean | string | number; + activeText?: string; + inactiveText?: string; + // input模板相关参数 + inputType?: string; + // price模板相关参数 + priceFormat?: string; + // date模板相关参数 + dateFormat?: string; + // tool模板相关参数 + operat?: Array< + | "edit" + | "delete" + | { + auth: string; + icon?: string; + name: string; + text: string; + type?: "primary" | "success" | "warning" | "danger" | "info"; + render?: (row: IObject) => boolean; + } + >; + // filter值拼接符 + filterJoin?: string; + [key: string]: any; + // 初始化数据函数 + initFn?: (item: IObject) => void; + }>; +} + +export interface IModalConfig { + // 页面名称 + pageName?: string; + // 主键名(主要用于编辑数据,默认为id) + pk?: string; + // 组件类型 + component?: "dialog" | "drawer"; + // dialog组件属性 + dialog?: Partial>; + // drawer组件属性 + drawer?: Partial>; + // form组件属性 + form?: IForm; + // 表单项 + formItems: IFormItems; + // 提交之前处理 + beforeSubmit?: (data: T) => void; + // 提交的网络请求函数(需返回promise) + formAction: (data: T) => Promise; +} + +export type IForm = Partial>; + +// 表单项 +export type IFormItems = Array<{ + // 组件类型(如input,select,radio,custom等,默认input) + type?: + | "input" + | "select" + | "radio" + | "switch" + | "checkbox" + | "tree-select" + | "date-picker" + | "input-number" + | "text" + | "custom"; + // 组件属性 + attrs?: IObject; + // 组件可选项(适用于select,radio,checkbox组件) + options?: Array<{ + label: string; + value: any; + disabled?: boolean; + [key: string]: any; + }>; + // 插槽名(适用于组件类型为custom) + slotName?: string; + // 标签文本 + label: string; + // 标签提示 + tips?: string; + // 键名 + prop: string; + // 验证规则 + rules?: FormItemRule[]; + // 初始值 + initialValue?: any; + // 是否隐藏 + hidden?: boolean; + // layout组件Col属性 + col?: Partial; + // 监听函数 + watch?: (newValue: any, oldValue: any, data: T, items: IObject[]) => void; + // 计算属性函数 + computed?: (data: T) => any; + // 监听收集函数 + watchEffect?: (data: T) => void; + // 初始化数据函数扩展 + initFn?: (item: IObject) => void; +}>; + +export interface IPageForm { + // 主键名(主要用于编辑数据,默认为id) + pk?: string; + // form组件属性 + form?: IForm; + // 表单项 + formItems: IFormItems; +} diff --git a/src/components/CURD/usePage.ts b/src/components/CURD/usePage.ts new file mode 100644 index 0000000..4d410ce --- /dev/null +++ b/src/components/CURD/usePage.ts @@ -0,0 +1,73 @@ +import { ref } from "vue"; +import type { + IObject, + PageContentInstance, + PageModalInstance, + PageSearchInstance, +} from "./types"; + +function usePage() { + const searchRef = ref(); + const contentRef = ref(); + const addModalRef = ref(); + const editModalRef = ref(); + + // 搜索 + function handleQueryClick(queryParams: IObject) { + const filterParams = contentRef.value?.getFilterParams(); + contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true); + } + // 重置 + function handleResetClick(queryParams: IObject) { + const filterParams = contentRef.value?.getFilterParams(); + contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true); + } + // 新增 + function handleAddClick() { + //显示添加表单 + addModalRef.value?.setModalVisible(); + } + // 编辑 + function handleEditClick(row: IObject) { + //显示编辑表单 根据数据进行填充 + editModalRef.value?.setModalVisible(row); + } + // 表单提交 + function handleSubmitClick() { + //根据检索条件刷新列表数据 + const queryParams = searchRef.value?.getQueryParams(); + contentRef.value?.fetchPageData(queryParams, true); + } + // 导出 + function handleExportClick() { + // 根据检索条件导出数据 + const queryParams = searchRef.value?.getQueryParams(); + contentRef.value?.exportPageData(queryParams); + } + // 搜索显隐 + function handleSearchClick() { + searchRef.value?.toggleVisible(); + } + // 涮选数据 + function handleFilterChange(filterParams: IObject) { + const queryParams = searchRef.value?.getQueryParams(); + contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true); + } + + return { + searchRef, + contentRef, + addModalRef, + editModalRef, + handleQueryClick, + handleResetClick, + handleAddClick, + handleEditClick, + handleSubmitClick, + handleExportClick, + handleSearchClick, + handleFilterChange, + }; +} + +export default usePage; diff --git a/src/components/CopyButton/index.vue b/src/components/CopyButton/index.vue new file mode 100644 index 0000000..8e88435 --- /dev/null +++ b/src/components/CopyButton/index.vue @@ -0,0 +1,62 @@ + + + + diff --git a/src/components/Dictionary/index.vue b/src/components/Dictionary/index.vue new file mode 100644 index 0000000..763c2c6 --- /dev/null +++ b/src/components/Dictionary/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/GithubCorner/index.vue b/src/components/GithubCorner/index.vue new file mode 100644 index 0000000..7f91959 --- /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..6ab2eb6 --- /dev/null +++ b/src/components/Hamburger/index.vue @@ -0,0 +1,39 @@ + + + + diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue new file mode 100644 index 0000000..e74aa44 --- /dev/null +++ b/src/components/IconSelect/index.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/components/IcpBar/index.vue b/src/components/IcpBar/index.vue new file mode 100644 index 0000000..a7ed859 --- /dev/null +++ b/src/components/IcpBar/index.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/components/LangSelect/index.vue b/src/components/LangSelect/index.vue new file mode 100644 index 0000000..2f35582 --- /dev/null +++ b/src/components/LangSelect/index.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/components/LoginDialog/index.vue b/src/components/LoginDialog/index.vue new file mode 100644 index 0000000..1ae461e --- /dev/null +++ b/src/components/LoginDialog/index.vue @@ -0,0 +1,472 @@ + + + + + diff --git a/src/components/MenuSearch/index.vue b/src/components/MenuSearch/index.vue new file mode 100644 index 0000000..0fc8afa --- /dev/null +++ b/src/components/MenuSearch/index.vue @@ -0,0 +1,143 @@ + + + + diff --git a/src/components/MsgTip/index.vue b/src/components/MsgTip/index.vue new file mode 100644 index 0000000..db3a172 --- /dev/null +++ b/src/components/MsgTip/index.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue new file mode 100644 index 0000000..c080e60 --- /dev/null +++ b/src/components/Pagination/index.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/components/SendCodeButton/index.vue b/src/components/SendCodeButton/index.vue new file mode 100644 index 0000000..a266481 --- /dev/null +++ b/src/components/SendCodeButton/index.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..d3e83fe --- /dev/null +++ b/src/components/SizeSelect/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..0fdd735 --- /dev/null +++ b/src/components/SvgIcon/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/TableSelect/index.vue b/src/components/TableSelect/index.vue new file mode 100644 index 0000000..28ad4b6 --- /dev/null +++ b/src/components/TableSelect/index.vue @@ -0,0 +1,371 @@ + + + + + diff --git a/src/components/TopBar/index.vue b/src/components/TopBar/index.vue new file mode 100644 index 0000000..374a9d8 --- /dev/null +++ b/src/components/TopBar/index.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/src/components/Upload/FileUpload.vue b/src/components/Upload/FileUpload.vue new file mode 100644 index 0000000..7c8251b --- /dev/null +++ b/src/components/Upload/FileUpload.vue @@ -0,0 +1,331 @@ + + + + diff --git a/src/components/Upload/ImageUpload.vue b/src/components/Upload/ImageUpload.vue new file mode 100644 index 0000000..0c8428b --- /dev/null +++ b/src/components/Upload/ImageUpload.vue @@ -0,0 +1,280 @@ + + + + diff --git a/src/components/WangEditor/index.vue b/src/components/WangEditor/index.vue new file mode 100644 index 0000000..aae60c8 --- /dev/null +++ b/src/components/WangEditor/index.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/directive/index.ts b/src/directive/index.ts new file mode 100644 index 0000000..9c22eb6 --- /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..7a080e3 --- /dev/null +++ b/src/directive/permission/index.ts @@ -0,0 +1,38 @@ +import { hasAuth } from "@/plugins/permission"; +import { Directive, DirectiveBinding } from "vue"; + +/** + * 按钮权限 + */ +export const hasPerm: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + // DOM绑定需要的按钮权限标识 + const { value: requiredPerms } = binding; + if (requiredPerms) { + if (!hasAuth(requiredPerms)) { + 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) { + // DOM绑定需要的角色编码 + const { value: requiredRoles } = binding; + if (requiredRoles) { + if (!hasAuth(requiredRoles, "role")) { + el.parentNode && el.parentNode.removeChild(el); + } + } else { + throw new Error("need roles! Like v-has-role=\"['admin','test']\""); + } + }, +}; diff --git a/src/enums/CacheEnum.ts b/src/enums/CacheEnum.ts new file mode 100644 index 0000000..f3c434b --- /dev/null +++ b/src/enums/CacheEnum.ts @@ -0,0 +1,4 @@ +/** + * 令牌缓存Key + */ +export const TOKEN_KEY = "accessToken"; diff --git a/src/enums/DeviceEnum.ts b/src/enums/DeviceEnum.ts new file mode 100644 index 0000000..709bcb3 --- /dev/null +++ b/src/enums/DeviceEnum.ts @@ -0,0 +1,14 @@ +/** + * 设备枚举 + */ +export const enum DeviceEnum { + /** + * 宽屏设备 + */ + DESKTOP = "desktop", + + /** + * 窄屏设备 + */ + MOBILE = "mobile", +} diff --git a/src/enums/DocumentTypeEnum.ts b/src/enums/DocumentTypeEnum.ts new file mode 100644 index 0000000..f4df798 --- /dev/null +++ b/src/enums/DocumentTypeEnum.ts @@ -0,0 +1,22 @@ +export const DocumentType = [ + { + value: 1, + label: "身份证", + }, + { + value: 2, + label: "营业执照", + }, + { + value: 3, + label: "学生证", + }, + { + value: 4, + label: "士兵证", + }, + { + value: 5, + label: "护照", + }, +]; diff --git a/src/enums/FormTypeEnum.ts b/src/enums/FormTypeEnum.ts new file mode 100644 index 0000000..583c33a --- /dev/null +++ b/src/enums/FormTypeEnum.ts @@ -0,0 +1,14 @@ +/** + * 表单类型枚举 + */ +export const FormTypeEnum: Record = { + INPUT: { value: 1, label: "输入框" }, + SELECT: { value: 2, label: "下拉框" }, + RADIO: { value: 3, label: "单选框" }, + CHECK_BOX: { value: 4, label: "复选框" }, + INPUT_NUMBER: { value: 5, label: "数字输入框" }, + SWITCH: { value: 6, label: "开关" }, + TEXT_AREA: { value: 7, label: "文本域" }, + DATE: { value: 8, label: "日期框" }, + DATE_TIME: { value: 9, label: "日期时间框" }, +}; diff --git a/src/enums/LanguageEnum.ts b/src/enums/LanguageEnum.ts new file mode 100644 index 0000000..a50b655 --- /dev/null +++ b/src/enums/LanguageEnum.ts @@ -0,0 +1,14 @@ +/** + * 语言枚举 + */ +export const enum LanguageEnum { + /** + * 中文 + */ + ZH_CN = "zh-cn", + + /** + * 英文 + */ + EN = "en", +} diff --git a/src/enums/LayoutEnum.ts b/src/enums/LayoutEnum.ts new file mode 100644 index 0000000..68be72e --- /dev/null +++ b/src/enums/LayoutEnum.ts @@ -0,0 +1,18 @@ +/** + * 菜单布局枚举 + */ +export const enum LayoutEnum { + /** + * 左侧菜单布局 + */ + LEFT = "left", + /** + * 顶部菜单布局 + */ + TOP = "top", + + /** + * 混合菜单布局 + */ + MIX = "mix", +} diff --git a/src/enums/MenuTypeEnum.ts b/src/enums/MenuTypeEnum.ts new file mode 100644 index 0000000..b69942f --- /dev/null +++ b/src/enums/MenuTypeEnum.ts @@ -0,0 +1,22 @@ +/** + * 菜单类型枚举 + */ +export const enum MenuTypeEnum { + /** + * 目录 + */ + CATALOG = "CATALOG", + /** + * 菜单 + */ + MENU = "MENU", + + /** + * 按钮 + */ + BUTTON = "BUTTON", + /** + * 外链 + */ + EXTLINK = "EXTLINK", +} diff --git a/src/enums/MessageTypeEnum.ts b/src/enums/MessageTypeEnum.ts new file mode 100644 index 0000000..16a3c97 --- /dev/null +++ b/src/enums/MessageTypeEnum.ts @@ -0,0 +1,36 @@ +/* 消息类型枚举 */ +export const enum MessageTypeEnum { + /* 公告 */ + NOTICE = 0, + /* 通知 */ + MESSAGE = 1, +} + +export const MessageTypeLabels = { + [MessageTypeEnum.NOTICE]: "公告", + [MessageTypeEnum.MESSAGE]: "通知", +}; + +export const MessageType = [ + { + value: 0, + label: "公告", + }, + { + value: 1, + label: "通知", + }, +]; + +export const rgbFind = /rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)/gi; +const rgbParse = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/i; +export function rgb2Hex(match: string) { + const parts = match.match(rgbParse); + if (parts?.length == 4) { + const r = parseInt(parts[1]); + const g = parseInt(parts[2]); + const b = parseInt(parts[3]); + return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + } + return match; +} diff --git a/src/enums/NoticeTypeEnum.ts b/src/enums/NoticeTypeEnum.ts new file mode 100644 index 0000000..49c0f8e --- /dev/null +++ b/src/enums/NoticeTypeEnum.ts @@ -0,0 +1,30 @@ +/* 通知类型枚举 */ +export const enum NoticeTypeEnum { + /** 系统升级 */ + SYSTEM_UPGRADE = "SYSTEM_UPGRADE", + /** 系统维护 */ + SYSTEM_MAINTENANCE = "SYSTEM_MAINTENANCE", + /** 安全警告 */ + SECURITY_ALERT = "SECURITY_ALERT", + /** 假期通知 */ + HOLIDAY_NOTICE = "HOLIDAY_NOTICE", + /** 公司新闻 */ + COMPANY_NEWS = "COMPANY_NEWS", + /** 其他通知 */ + OTHER = "OTHER", +} + +// 定义标签映射 +const NoticeTypeLabels: Record = { + [NoticeTypeEnum.SYSTEM_UPGRADE]: "系统升级", + [NoticeTypeEnum.SYSTEM_MAINTENANCE]: "系统维护", + [NoticeTypeEnum.SECURITY_ALERT]: "安全警告", + [NoticeTypeEnum.HOLIDAY_NOTICE]: "假期通知", + [NoticeTypeEnum.COMPANY_NEWS]: "公司新闻", + [NoticeTypeEnum.OTHER]: "其他通知", +}; + +// 导出获取标签函数 +export const getNoticeLabel = (type: NoticeTypeEnum): string => { + return NoticeTypeLabels[type] || ""; +}; diff --git a/src/enums/QueryTypeEnum.ts b/src/enums/QueryTypeEnum.ts new file mode 100644 index 0000000..253efc0 --- /dev/null +++ b/src/enums/QueryTypeEnum.ts @@ -0,0 +1,37 @@ +/** + * 查询类型枚举 + */ +export const QueryTypeEnum: Record = { + /** 等于 */ + EQ: { value: 1, label: "=" }, + + /** 模糊匹配 */ + LIKE: { value: 2, label: "LIKE '%s%'" }, + + /** 包含 */ + IN: { value: 3, label: "IN" }, + + /** 范围 */ + BETWEEN: { value: 4, label: "BETWEEN" }, + + /** 大于 */ + GT: { value: 5, label: ">" }, + + /** 大于等于 */ + GE: { value: 6, label: ">=" }, + + /** 小于 */ + LT: { value: 7, label: "<" }, + + /** 小于等于 */ + LE: { value: 8, label: "<=" }, + + /** 不等于 */ + NE: { value: 9, label: "!=" }, + + /** 左模糊匹配 */ + LIKE_LEFT: { value: 10, label: "LIKE '%s'" }, + + /** 右模糊匹配 */ + LIKE_RIGHT: { value: 11, label: "LIKE 's%'" }, +}; diff --git a/src/enums/ResultEnum.ts b/src/enums/ResultEnum.ts new file mode 100644 index 0000000..6e3f931 --- /dev/null +++ b/src/enums/ResultEnum.ts @@ -0,0 +1,23 @@ +/** + * 响应码枚举 + */ +export const enum ResultEnum { + /** + * 成功 + */ + SUCCESS = "00000", + /** + * 错误 + */ + ERROR = "B0001", + + /** + * 令牌无效或过期 + */ + TOKEN_INVALID = "A0230", + + /** + * 用户已注册 + */ + USER_NAME_EXISTED = "A0206", +} diff --git a/src/enums/SidebarStatusEnum.ts b/src/enums/SidebarStatusEnum.ts new file mode 100644 index 0000000..a0d877a --- /dev/null +++ b/src/enums/SidebarStatusEnum.ts @@ -0,0 +1,14 @@ +/** + * 侧边栏状态枚举 + */ +export const enum SidebarStatusEnum { + /** + * 展开 + */ + OPENED = "opened", + + /** + * 关闭 + */ + CLOSED = "closed", +} diff --git a/src/enums/SizeEnum.ts b/src/enums/SizeEnum.ts new file mode 100644 index 0000000..e91e913 --- /dev/null +++ b/src/enums/SizeEnum.ts @@ -0,0 +1,19 @@ +/** + * 布局大小枚举 + */ +export const enum SizeEnum { + /** + * 默认 + */ + DEFAULT = "default", + + /** + * 大型 + */ + LARGE = "large", + + /** + * 小型 + */ + SMALL = "small", +} diff --git a/src/enums/SoftwareEnum.ts b/src/enums/SoftwareEnum.ts new file mode 100644 index 0000000..20fbb55 --- /dev/null +++ b/src/enums/SoftwareEnum.ts @@ -0,0 +1,81 @@ +export interface ColoredType { + color: string; + value: number; + label: string; +} + +export const softwareTypes: ColoredType[] = [ + { + color: "#6222C9", + value: 0, + label: "风格工具", + }, + { + color: "#000", + value: 1, + label: "", + }, + { + color: "#E63415", + value: 2, + label: "唱作魔方", + }, + { + color: "#1EC79D", + value: 3, + label: "唱作魔方Pro", + }, + { + color: "#4167F0", + value: 4, + label: "唱作网App", + }, +]; + +export const innerTypes: ColoredType[] = [ + { + color: "#E63415", + value: 0, + label: "ac_win", + }, + { + color: "#42A2F0", + value: 1, + label: "cz_android", + }, + { + color: "#4167F0", + value: 2, + label: "cz_iOS", + }, + { + color: "#FF6600", + value: 3, + label: "ac_android", + }, + { + color: "#F05650", + value: 4, + label: "ac_iOS", + }, + { + color: "#000", + value: 5, + label: "web", + }, + { + color: "#1EC79D", + value: 6, + label: "pro_win", + }, + { + color: "#6222C9", + value: 7, + label: "styleTool", + }, + { + color: "#14CCCC", + value: 8, + label: "proLite", + }, +]; diff --git a/src/enums/ThemeEnum.ts b/src/enums/ThemeEnum.ts new file mode 100644 index 0000000..91458e1 --- /dev/null +++ b/src/enums/ThemeEnum.ts @@ -0,0 +1,18 @@ +/** + * 主题枚举 + */ +export const enum ThemeEnum { + /** + * 明亮主题 + */ + LIGHT = "light", + /** + * 暗黑主题 + */ + DARK = "dark", + + /** + * 系统自动 + */ + AUTO = "auto", +} diff --git a/src/lang/index.ts b/src/lang/index.ts new file mode 100644 index 0000000..ec3e6cc --- /dev/null +++ b/src/lang/index.ts @@ -0,0 +1,31 @@ +import type { App } from "vue"; +import { createI18n } from "vue-i18n"; +import { useAppStoreHook } from "@/store/modules/app"; +// 本地语言包 +import enLocale from "./package/en"; +import zhCnLocale from "./package/zh-cn"; + +const appStore = useAppStoreHook(); + +const messages = { + "zh-cn": { + ...zhCnLocale, + }, + en: { + ...enLocale, + }, +}; + +const i18n = createI18n({ + legacy: false, + locale: appStore.language, + messages: messages, + globalInjection: true, +}); + +// 全局注册 i18n +export function setupI18n(app: App) { + app.use(i18n); +} + +export default i18n; diff --git a/src/lang/package/en.ts b/src/lang/package/en.ts new file mode 100644 index 0000000..c1085bd --- /dev/null +++ b/src/lang/package/en.ts @@ -0,0 +1,64 @@ +import { register } from "module"; + +export default { + // 路由国际化 + route: { + dashboard: "Dashboard", + document: "Document", + }, + // 登录页面国际化 + login: { + username: "Username", + password: "Password", + login: "Login", + register: "Register", + captchaCode: "Verify Code", + capsLock: "Caps Lock is On", + message: { + username: { + required: "Please enter Username", + }, + password: { + required: "Please enter Password", + min: "The password can not be less than 6 digits", + }, + captchaCode: { + required: "Please enter Verify Code", + }, + }, + }, + // 导航栏国际化 + navbar: { + dashboard: "Dashboard", + logout: "Logout", + document: "Document", + gitee: "Gitee", + system: "SystemAdmin", + website: "WebsiteAdmin", + }, + sizeSelect: { + tooltip: "Layout Size", + default: "Default", + large: "Large", + small: "Small", + message: { + success: "Switch Layout Size Successful!", + }, + }, + langSelect: { + message: { + success: "Switch Language Successful!", + }, + }, + settings: { + project: "Project Settings", + theme: "Theme", + interface: "Interface", + navigation: "Navigation", + themeColor: "Theme Color", + tagsView: "Tags View", + fixedHeader: "Fixed Header", + sidebarLogo: "Sidebar Logo", + watermark: "Watermark", + }, +}; diff --git a/src/lang/package/zh-cn.ts b/src/lang/package/zh-cn.ts new file mode 100644 index 0000000..1d9e4a5 --- /dev/null +++ b/src/lang/package/zh-cn.ts @@ -0,0 +1,63 @@ +export default { + // 路由国际化 + route: { + dashboard: "首页", + document: "项目文档", + }, + // 登录页面国际化 + login: { + username: "用户名", + password: "密码", + login: "登 录", + register: "注 册", + captchaCode: "验证码", + capsLock: "大写锁定已打开", + message: { + username: { + required: "请输入用户名", + }, + password: { + required: "请输入密码", + min: "密码不能少于6位", + }, + captchaCode: { + required: "请输入验证码", + }, + }, + }, + // 导航栏国际化 + navbar: { + dashboard: "首页", + logout: "退出登录", + document: "项目文档", + gitee: "项目地址", + account: "账户管理", + system: "系统管理", + website: "网站管理", + }, + sizeSelect: { + tooltip: "布局大小", + default: "默认", + large: "大型", + small: "小型", + message: { + success: "切换布局大小成功!", + }, + }, + langSelect: { + message: { + success: "切换语言成功!", + }, + }, + settings: { + project: "项目配置", + theme: "主题设置", + interface: "界面设置", + navigation: "导航设置", + themeColor: "主题颜色", + tagsView: "开启 Tags-View", + fixedHeader: "固定 Header", + sidebarLogo: "侧边栏 Logo", + watermark: "开启水印", + }, +}; diff --git a/src/layout/components/AppMain/index.vue b/src/layout/components/AppMain/index.vue new file mode 100644 index 0000000..93f4a79 --- /dev/null +++ b/src/layout/components/AppMain/index.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/layout/components/NavBar/components/NavbarAction.vue b/src/layout/components/NavBar/components/NavbarAction.vue new file mode 100644 index 0000000..0e23e27 --- /dev/null +++ b/src/layout/components/NavBar/components/NavbarAction.vue @@ -0,0 +1,100 @@ + + + diff --git a/src/layout/components/NavBar/index.vue b/src/layout/components/NavBar/index.vue new file mode 100644 index 0000000..3759f16 --- /dev/null +++ b/src/layout/components/NavBar/index.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/layout/components/Settings/components/LayoutSelect.vue b/src/layout/components/Settings/components/LayoutSelect.vue new file mode 100644 index 0000000..0b880c1 --- /dev/null +++ b/src/layout/components/Settings/components/LayoutSelect.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/layout/components/Settings/components/ThemeColorPicker.vue b/src/layout/components/Settings/components/ThemeColorPicker.vue new file mode 100644 index 0000000..5a0d59c --- /dev/null +++ b/src/layout/components/Settings/components/ThemeColorPicker.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..619f3c3 --- /dev/null +++ b/src/layout/components/Settings/index.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/src/layout/components/Sidebar/components/SidebarLogo.vue b/src/layout/components/Sidebar/components/SidebarLogo.vue new file mode 100644 index 0000000..4da1795 --- /dev/null +++ b/src/layout/components/Sidebar/components/SidebarLogo.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/layout/components/Sidebar/components/SidebarMenu.vue b/src/layout/components/Sidebar/components/SidebarMenu.vue new file mode 100644 index 0000000..526a24c --- /dev/null +++ b/src/layout/components/Sidebar/components/SidebarMenu.vue @@ -0,0 +1,89 @@ + + + + diff --git a/src/layout/components/Sidebar/components/SidebarMenuItem.vue b/src/layout/components/Sidebar/components/SidebarMenuItem.vue new file mode 100644 index 0000000..e45ec8d --- /dev/null +++ b/src/layout/components/Sidebar/components/SidebarMenuItem.vue @@ -0,0 +1,202 @@ + + + + diff --git a/src/layout/components/Sidebar/components/SidebarMenuItemTitle.vue b/src/layout/components/Sidebar/components/SidebarMenuItemTitle.vue new file mode 100644 index 0000000..06bfef1 --- /dev/null +++ b/src/layout/components/Sidebar/components/SidebarMenuItemTitle.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/layout/components/Sidebar/components/SidebarMixTopMenu.vue b/src/layout/components/Sidebar/components/SidebarMixTopMenu.vue new file mode 100644 index 0000000..6c43115 --- /dev/null +++ b/src/layout/components/Sidebar/components/SidebarMixTopMenu.vue @@ -0,0 +1,88 @@ + + + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..59c6b6b --- /dev/null +++ b/src/layout/components/Sidebar/index.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..32dede7 --- /dev/null +++ b/src/layout/components/TagsView/index.vue @@ -0,0 +1,433 @@ + + + + + diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..1763da9 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,298 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..a301662 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,18 @@ +import { createApp } from "vue"; +import App from "./App.vue"; +import setupPlugins from "@/plugins"; +import VueQrcode from "vue-qrcode"; + +// 本地SVG图标 +import "virtual:svg-icons-register"; + +// 样式 +import "uno.css"; +import "element-plus/theme-chalk/dark/css-vars.css"; +import "@/styles/index.scss"; +import "animate.css"; + +const app = createApp(App); +app.use(setupPlugins); +app.component("vue-qrcode", VueQrcode); +app.mount("#app"); diff --git a/src/plugins/icons.ts b/src/plugins/icons.ts new file mode 100644 index 0000000..fa85ba1 --- /dev/null +++ b/src/plugins/icons.ts @@ -0,0 +1,9 @@ +import type { App } from "vue"; +import * as ElementPlusIconsVue from "@element-plus/icons-vue"; + +// 注册所有图标 +export function setupElIcons(app: App) { + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component); + } +} diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 0000000..611f601 --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1,24 @@ +import type { App } from "vue"; +import { setupDirective } from "@/directive"; +import { setupI18n } from "@/lang"; +import { setupRouter } from "@/router"; +import { setupStore } from "@/store"; +import { setupElIcons } from "./icons"; +import { setupPermission } from "./permission"; + +export default { + install(app: App) { + // 自定义指令(directive) + setupDirective(app); + // 路由(router) + setupRouter(app); + // 状态管理(store) + setupStore(app); + // 国际化 + setupI18n(app); + // Element-plus图标 + setupElIcons(app); + // 路由守卫 + setupPermission(); + }, +}; diff --git a/src/plugins/permission.ts b/src/plugins/permission.ts new file mode 100644 index 0000000..9f85a21 --- /dev/null +++ b/src/plugins/permission.ts @@ -0,0 +1,111 @@ +import { NavigationGuardNext, RouteLocationNormalized, RouteRecordRaw } from "vue-router"; + +import NProgress from "@/utils/nprogress"; +import { TOKEN_KEY } from "@/enums/CacheEnum"; +import router from "@/router"; +import { usePermissionStore, useUserStore } from "@/store"; + +export function setupPermission() { + // 白名单路由 + const whiteList = ["/software", "/helper"]; + + const allowPath = (path: string) => { + return whiteList.some((item) => { + return path == item || path.indexOf(item + "/") >= 0; + }); + }; + + router.beforeEach(async (to, from, next) => { + NProgress.start(); + + if (to.path.indexOf("/autoLogin") >= 0 || to.path.indexOf("/resetPassword") >= 0) { + next(); + return; + } + + const hasToken = localStorage.getItem(TOKEN_KEY); + if (hasToken) { + // if (to.path === "/software") { + // // 如果已登录,跳转到首页 + // next({ path: "/" }); + // NProgress.done(); + // } else { + const userStore = useUserStore(); + if (hasToken != userStore.sessionToken) { + // console.log(hasToken); + // console.log(userStore.sessionToken); + userStore.sessionToken = hasToken; + userStore.clearUser(); + } + + const hasRoles = userStore.user.roles && userStore.user.roles.length > 0; + if (hasRoles) { + // 如果未匹配到任何路由,跳转到404页面 + if (to.matched.length === 0) { + next(from.name ? { name: from.name } : "/404"); + } else { + // 如果路由参数中有 title,覆盖路由元信息中的 title + const title = (to.params.title as string) || (to.query.title as string); + if (title) { + to.meta.title = title; + } + next(); + } + } else { + const permissionStore = usePermissionStore(); + try { + await userStore.getUserInfo(); + const dynamicRoutes = await permissionStore.generateRoutes(); + dynamicRoutes.forEach((route: RouteRecordRaw) => router.addRoute(route)); + + userStore.getAccountInfo(); + userStore.getLatestMessage(); + + next({ ...to, replace: true }); + } catch (error) { + // 移除 token 并重定向到登录页,携带当前页面路由作为跳转参数 + await userStore.resetToken(); + redirectToLogin(to, next); + NProgress.done(); + } + } + // } + } else { + // 未登录 + if (allowPath(to.path)) { + // console.log("good path"); + next(); // 在白名单,直接进入 + } else { + // 不在白名单,重定向到登录页 + redirectToLogin(to, next); + NProgress.done(); + } + } + }); + + router.afterEach(() => { + NProgress.done(); + }); +} + +/** 重定向到登录页 */ +function redirectToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) { + const params = new URLSearchParams(to.query as Record); + const queryString = params.toString(); + const redirect = queryString ? `${to.path}?${queryString}` : to.path; + next(`/software`); + // next(`/software?redirect=${encodeURIComponent(redirect)}`); +} + +/** 判断是否有权限 */ +export function hasAuth(value: string | string[], type: "button" | "role" = "button") { + const { roles, perms } = useUserStore().user; + + // 超级管理员 拥有所有权限 + if (type === "button" && roles.includes("ROOT")) { + return true; + } + + const auths = type === "button" ? perms : roles; + return typeof value === "string" ? auths.includes(value) : value.some((perm) => auths.includes(perm)); +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..94f4b3d --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,168 @@ +import path from "path"; +import type { App } from "vue"; +import { createRouter, createWebHistory, 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: "/autoLogin", + name: "AutoLogin", + component: () => import("@/views/autoLogin/index.vue"), + meta: { hidden: true }, + }, + { + path: "/software", + name: "Software", + component: () => import("@/views/software/index.vue"), + meta: { hidden: true }, + children: [ + { + path: "id/:id/os/:os", + component: () => import("@/views/software/index.vue"), + children: [{ path: "fonts/:fonts", component: () => import("@/views/software/index.vue") }], + }, + ], + }, + { + path: "/helper", + name: "Helper", + component: () => import("@/views/helper/index.vue"), + meta: { hidden: true }, + children: [ + { + path: "id/:id", + component: () => import("@/views/helper/index.vue"), + }, + { + path: "page/:page", + component: () => import("@/views/helper/index.vue"), + }, + ], + }, + { + path: "/resetPassword", + name: "ResetPassword", + component: () => import("@/views/resetPassword/index.vue"), + meta: { hidden: true }, + }, + { + path: "/", + name: "/", + component: Layout, + redirect: "/software", + children: [ + { + path: "dashboard", + component: () => import("@/views/dashboard/index.vue"), + // 用于 keep-alive 功能,需要与 SFC 中自动推导或显式声明的组件名称一致 + // 参考文档: https://cn.vuejs.org/guide/built-ins/keep-alive.html#include-exclude + name: "Dashboard", + meta: { + title: "账号信息", + icon: "homepage", + affix: true, + keepAlive: 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: [ { + // component: () => import("@/views/external-link/index.vue"), + // 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: createWebHistory(), // createWebHashHistory(), + routes: constantRoutes, + // 刷新时,滚动条位置还原 + scrollBehavior: () => ({ left: 0, top: 0 }), +}); + +// 全局注册 router +export function setupRouter(app: App) { + app.use(router); +} + +/** + * 重置路由 + */ +export function resetRouter() { + router.replace({ path: "/index" }); +} + +export default router; diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..f6b4d30 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,26 @@ +import { SizeEnum } from "./enums/SizeEnum"; +import { LayoutEnum } from "./enums/LayoutEnum"; +import { ThemeEnum } from "./enums/ThemeEnum"; +import { LanguageEnum } from "./enums/LanguageEnum"; + +const { pkg } = __APP_INFO__; + +const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)"); + +const defaultSettings: AppSettings = { + title: "唱作网", + version: pkg.version, + showSettings: true, + tagsView: true, + fixedHeader: true, + sidebarLogo: true, + layout: LayoutEnum.LEFT, + theme: mediaQueryList.matches ? ThemeEnum.DARK : ThemeEnum.LIGHT, + size: SizeEnum.DEFAULT, + language: LanguageEnum.ZH_CN, + themeColor: "#409EFF", + watermarkEnabled: false, + watermarkContent: pkg.name, +}; + +export default defaultSettings; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..8dd6ea9 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,16 @@ +import type { App } from "vue"; +import { createPinia } from "pinia"; + +const store = createPinia(); + +// 全局注册 store +export function setupStore(app: App) { + app.use(store); +} + +export * from "./modules/app"; +export * from "./modules/permission"; +export * from "./modules/settings"; +export * from "./modules/tagsView"; +export * from "./modules/user"; +export { store }; diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..04447f2 --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,154 @@ +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"; +import { store } from "@/store"; +import { DeviceEnum } from "@/enums/DeviceEnum"; +import { SidebarStatusEnum } from "@/enums/SidebarStatusEnum"; + +export const useAppStore = defineStore("app", () => { + // 设备类型 + const device = useStorage("device", DeviceEnum.DESKTOP); + // 布局大小 + const size = useStorage("size", defaultSettings.size); + // 语言 + const language = useStorage("language", defaultSettings.language); + // 侧边栏状态 + const sidebarStatus = useStorage("sidebarStatus", SidebarStatusEnum.CLOSED); + const sidebar = reactive({ + opened: sidebarStatus.value === SidebarStatusEnum.OPENED, + withoutAnimation: false, + }); + + // 顶部菜单激活路径 + const activeTopMenuPath = useStorage("activeTopMenuPath", ""); + + /*********************************************************************** + * 全局验证码发送倒计时 + */ + const SEND_CODE_EXPIRE = 120; // 注册验证码发送间隔 + let sendCodeTimer: any = null; // 定时器 + const sendCodeDuration = ref(0); //倒计时 + const sendCodeTime = useStorage("scTime", 0); //发送时间 + /** 启动初始化 */ + if (sendCodeTime.value > 0) { + const duration = Math.floor(Date.now() / 1000) - sendCodeTime.value; + // console.log(duration); + if (duration > 0 && duration < SEND_CODE_EXPIRE) { + sendCodeDuration.value = SEND_CODE_EXPIRE - duration; + sendCodeTimer = setInterval(fnCountdown(), 1000); + } + } + /** 定时器函数 */ + function fnCountdown() { + if (sendCodeDuration.value-- <= 0) { + // 清除掉定时器 + clearInterval(sendCodeTimer); + sendCodeTimer = null; + } + return fnCountdown; + } + /** 启动倒计时 */ + function startSendCodeCountdown() { + sendCodeTimer && clearInterval(sendCodeTimer); //保证计时器唯一 + + sendCodeTime.value = Math.floor(Date.now() / 1000); //倒计时起始时间 + sendCodeDuration.value = SEND_CODE_EXPIRE; + + sendCodeTimer = setInterval(fnCountdown(), 1000); + } + /** 停止倒计时 */ + function stopSendCodeCountdown() { + sendCodeTimer && clearInterval(sendCodeTimer); + + sendCodeTime.value = 0; // 清空倒计时时间 + sendCodeDuration.value = 0; + } + /****************************************************** */ + + /** + * 根据语言标识读取对应的语言包 + */ + const locale = computed(() => { + if (language?.value == "en") { + return en; + } else { + return zhCn; + } + }); + + // 切换侧边栏 + function toggleSidebar() { + sidebar.opened = !sidebar.opened; + sidebarStatus.value = sidebar.opened ? SidebarStatusEnum.OPENED : SidebarStatusEnum.CLOSED; + } + + // 关闭侧边栏 + function closeSideBar() { + sidebar.opened = false; + sidebarStatus.value = SidebarStatusEnum.CLOSED; + } + + // 打开侧边栏 + function openSideBar() { + sidebar.opened = true; + sidebarStatus.value = SidebarStatusEnum.OPENED; + } + + // 切换设备 + function toggleDevice(val: string) { + device.value = val; + } + + /** + * 改变布局大小 + * + * @param val 布局大小 default | small | large + */ + function changeSize(val: string) { + size.value = val; + } + /** + * 切换语言 + * + * @param val + */ + function changeLanguage(val: string) { + language.value = val; + } + /** + * 混合模式顶部切换 + */ + function activeTopMenu(val: string) { + activeTopMenuPath.value = val; + } + + return { + device, + sidebar, + language, + locale, + size, + activeTopMenu, + toggleDevice, + changeSize, + changeLanguage, + toggleSidebar, + closeSideBar, + openSideBar, + activeTopMenuPath, + sendCodeDuration, + startSendCodeCountdown, + stopSendCodeCountdown, + }; +}); + +/** + * 用于在组件外部(如在Pinia Store 中)使用 Pinia 提供的 store 实例。 + * 官方文档解释了如何在组件外部使用 Pinia Store: + * https://pinia.vuejs.org/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component + */ +export function useAppStoreHook() { + return useAppStore(store); +} diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..737803f --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,88 @@ +import { RouteRecordRaw } from "vue-router"; +import { constantRoutes } from "@/router"; +import { store } from "@/store"; +import MenuAPI, { RouteVO } from "@/api/menu"; + +const modules = import.meta.glob("../../views/**/**.vue"); +const Layout = () => import("@/layout/index.vue"); + +export const usePermissionStore = defineStore("permission", () => { + /** 所有路由,包括静态和动态路由 */ + const routes = ref([]); + /** 混合模式左侧菜单 */ + const mixLeftMenus = ref([]); + + /** + * 生成动态路由 + */ + function generateRoutes() { + return new Promise((resolve, reject) => { + MenuAPI.getRoutes() + .then((data) => { + const dynamicRoutes = transformRoutes(data); + routes.value = constantRoutes.concat(dynamicRoutes); + resolve(dynamicRoutes); + }) + .catch((error) => { + reject(error); + }); + }); + } + + /** + * 混合模式菜单下根据顶部菜单路径设置左侧菜单 + * + * @param topMenuPath - 顶部菜单路径 + */ + const setMixLeftMenus = (topMenuPath: string) => { + const matchedItem = routes.value.find((item) => item.path === topMenuPath); + if (matchedItem && matchedItem.children) { + mixLeftMenus.value = matchedItem.children; + } + }; + + return { + routes, + generateRoutes, + mixLeftMenus, + setMixLeftMenus, + }; +}); + +/** + * 转换路由数据为组件 + */ +const transformRoutes = (routes: RouteVO[]) => { + const asyncRoutes: RouteRecordRaw[] = []; + routes.forEach((route) => { + const tmpRoute = { ...route } as RouteRecordRaw; + // 顶级目录,替换为 Layout 组件 + if (tmpRoute.component?.toString() == "Layout") { + tmpRoute.component = Layout; + } 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 = transformRoutes(route.children); + } + + asyncRoutes.push(tmpRoute); + }); + + return asyncRoutes; +}; + +/** + * 在组件外使用 Pinia store 实例 + * @see https://pinia.vuejs.org/core-concepts/outside-component-usage.html + */ +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..a581e90 --- /dev/null +++ b/src/store/modules/settings.ts @@ -0,0 +1,125 @@ +import defaultSettings from "@/settings"; +import { ThemeEnum } from "@/enums/ThemeEnum"; +import Color from "color"; + +type SettingsValue = boolean | string; + +export const useSettingsStore = defineStore("setting", () => { + // 是否显示设置 + const settingsVisible = ref(false); + // 是否显示标签视图 + const tagsView = useStorage("tagsView", defaultSettings.tagsView); + // 是否显示侧边栏logo + const sidebarLogo = useStorage( + "sidebarLogo", + defaultSettings.sidebarLogo + ); + // 是否固定头部 + const fixedHeader = useStorage( + "fixedHeader", + defaultSettings.fixedHeader + ); + // 布局模式:left-左侧模式(默认) top-顶部模式 mix-混合模式 + const layout = useStorage("layout", defaultSettings.layout); + // 主题颜色 + const themeColor = useStorage( + "themeColor", + defaultSettings.themeColor + ); + // 主题:light-亮色(默认) dark-暗色 + const theme = useStorage("theme", defaultSettings.theme); + // 是否开启水印 + const watermarkEnabled = useStorage( + "watermarkEnabled", + defaultSettings.watermarkEnabled + ); + + watch( + [theme, themeColor], + ([newTheme, newThemeColor], [oldTheme, oldThemeColor]) => { + if (newTheme !== oldTheme) { + if (newTheme === ThemeEnum.DARK) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } + } + + if (newThemeColor !== oldThemeColor) { + const rootStyle = document.documentElement.style; + rootStyle.setProperty(`--el-color-primary`, newThemeColor); + rootStyle.setProperty(`--el-color-primary-dark-2`, newThemeColor); + + for (let i = 1; i < 10; i++) { + rootStyle.setProperty( + `--el-color-primary-light-${i}`, + `${Color(newThemeColor).alpha(1 - i * 0.1)}` + ); + } + } + }, + { + immediate: true, // 立即执行,确保在侦听器创建时执行一次 + } + ); + + const settingsMap: Record> = { + fixedHeader, + tagsView, + sidebarLogo, + layout, + watermarkEnabled, + }; + + function changeSetting({ + key, + value, + }: { + key: string; + value: SettingsValue; + }) { + const setting = settingsMap[key]; + if (setting) { + setting.value = value; + } + } + + /** + * 切换主题 + */ + function changeTheme(val: string) { + theme.value = val; + } + + /** + * 切换主题颜色 + * + * @param color 主题颜色 + * + */ + function changeThemeColor(color: string) { + themeColor.value = color; + } + + /** + * 切换布局 + */ + function changeLayout(val: string) { + layout.value = val; + } + + return { + settingsVisible, + tagsView, + fixedHeader, + sidebarLogo, + layout, + themeColor, + theme, + watermarkEnabled, + changeSetting, + changeTheme, + changeThemeColor, + changeLayout, + }; +}); diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts new file mode 100644 index 0000000..67137f1 --- /dev/null +++ b/src/store/modules/tagsView.ts @@ -0,0 +1,253 @@ +export const useTagsViewStore = defineStore("tagsView", () => { + const visitedViews = ref([]); + const cachedViews = ref([]); + const router = useRouter(); + const route = useRoute(); + + /** + * 添加已访问视图到已访问视图列表中 + */ + function addVisitedView(view: TagView) { + // 如果已经存在于已访问的视图列表中,则不再添加 + if (visitedViews.value.some((v) => v.path === view.path)) { + return; + } + // 如果视图是固定的(affix),则在已访问的视图列表的开头添加 + if (view.affix) { + visitedViews.value.unshift(view); + } else { + // 如果视图不是固定的,则在已访问的视图列表的末尾添加 + visitedViews.value.push(view); + } + } + + /** + * 添加缓存视图到缓存视图列表中 + */ + function addCachedView(view: TagView) { + const viewName = view.name; + // 如果缓存视图名称已经存在于缓存视图列表中,则不再添加 + if (cachedViews.value.includes(viewName)) { + return; + } + + // 如果视图需要缓存(keepAlive),则将其路由名称添加到缓存视图列表中 + if (view.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; + 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?.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) => { + if (index >= currIndex || item?.affix) { + return true; + } + + const cacheIndex = cachedViews.value.indexOf(item.name); + 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) => { + if (index <= currIndex || item?.affix) { + return true; + } + }); + resolve({ + visitedViews: [...visitedViews.value], + }); + }); + } + + function delAllViews() { + return new Promise((resolve) => { + const affixTags = visitedViews.value.filter((tag) => tag?.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?.affix); + visitedViews.value = affixTags; + resolve([...visitedViews.value]); + }); + } + + function delAllCachedViews() { + return new Promise((resolve) => { + cachedViews.value = []; + resolve([...cachedViews.value]); + }); + } + + /** + * 关闭当前tagView + */ + function closeCurrentView() { + const tags: TagView = { + name: route.name as string, + title: route.meta.title as string, + path: route.path, + fullPath: route.fullPath, + affix: route.meta?.affix, + keepAlive: route.meta?.keepAlive, + query: route.query, + }; + delView(tags).then((res: any) => { + if (isActive(tags)) { + toLastView(res.visitedViews, tags); + } + }); + } + + function isActive(tag: TagView) { + return tag.path === route.path; + } + + function toLastView(visitedViews: TagView[], view?: TagView) { + const latestView = visitedViews.slice(-1)[0]; + if (latestView && latestView.fullPath) { + router.push(latestView.fullPath); + } else { + // now the default is to redirect to the home page if there is no tags-view, + // you can adjust it according to your needs. + if (view?.name === "Dashboard") { + // to reload home page + router.replace("/redirect" + view.fullPath); + } else { + router.push("/"); + } + } + } + + return { + visitedViews, + cachedViews, + addVisitedView, + addCachedView, + delVisitedView, + delCachedView, + delOtherVisitedViews, + delOtherCachedViews, + updateVisitedView, + addView, + delView, + delOtherViews, + delLeftViews, + delRightViews, + delAllViews, + delAllVisitedViews, + delAllCachedViews, + closeCurrentView, + isActive, + toLastView, + }; +}); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..96ae229 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,214 @@ +import AuthAPI, { type LoginData, type RegisterData } from "@/api/auth"; +import UserAPI, { type UserInfo, AccountInfo } from "@/api/user"; +import MessageAPI from "@/api/message"; +import { resetRouter } from "@/router"; +import { store } from "@/store"; +import { TOKEN_KEY } from "@/enums/CacheEnum"; +import { WebMessage } from "@/api/message"; + +export const useUserStore = defineStore("user", () => { + const sessionToken = ref(); + const user = ref({ + roles: [], + perms: [], + }); + const account = ref({}); + const msgLatest = ref([]); + + function clearUser() { + // user.value.avatar = undefined; + user.value = { roles: [], perms: [] }; + account.value = {}; + msgLatest.value = []; + } + /** + * 登录 + * + * @param {LoginData} + * @returns + */ + function login(loginData: LoginData) { + return new Promise((resolve, reject) => { + AuthAPI.login(loginData) + .then((data) => { + const { tokenType, accessToken } = data; + const newToken = tokenType + " " + accessToken; + localStorage.setItem(TOKEN_KEY, newToken); + sessionToken.value = newToken; + + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); + } + + /** + * 注册 + * @returns + */ + function autoLogin(username: string, password: string) { + return new Promise((resolve, reject) => { + AuthAPI.autoLogin(username, password) + .then((data) => { + const { tokenType, accessToken } = data; + const newToken = tokenType + " " + accessToken; + localStorage.setItem(TOKEN_KEY, newToken); + sessionToken.value = newToken; + + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); + } + + /** + * 注册 + * @returns + */ + function register(regData: RegisterData) { + return new Promise((resolve, reject) => { + AuthAPI.register(regData) + .then((data) => { + const { tokenType, accessToken } = data; + localStorage.setItem(TOKEN_KEY, tokenType + " " + accessToken); // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); + } + + // 获取信息(用户昵称、头像、角色集合、权限集合) + function getUserInfo() { + return new Promise((resolve, reject) => { + UserAPI.getInfo() + .then((data) => { + if (!data) { + reject("Verification failed, please Login again."); + return; + } + if (!data.roles || data.roles.length <= 0) { + reject("getUserInfo: roles must be a non-null array!"); + return; + } + Object.assign(user.value, { ...data }); + resolve(data); + }) + .catch((error) => { + reject(error); + }); + }); + } + + let timerAccount: any = null; + + // 获取账户信息(实名认证、唱作音符、VIP等) + function getAccountInfo() { + function fn() { + UserAPI.getAccountInfo().then((data) => { + if (data) { + Object.assign(account.value, { ...data }); + } + }); + + return fn; + } + + timerMessage = setInterval(fn(), 600000); //10分钟刷新账户信息 + } + + let timerMessage: any = null; + + function getLatestMessage() { + function fn() { + MessageAPI.getLatest(user.value.userId!).then((data) => { + if (data) { + Object.assign(msgLatest.value, data); + // console.log(msgLatest.value); + } + }); + + return fn; + } + + timerMessage = setInterval(fn(), 900000); //15分钟刷新通知消息 + } + + function setMessageState(ids: number[], state: number, callApi: boolean) { + for (let i = msgLatest.value.length - 1; i >= 0; i--) { + const msg = msgLatest.value[i]; + if (ids.includes(msg.msgId!)) { + if (state == 1) msg.state = 1; + else msgLatest.value.splice(i, 1); + } + } + + if (callApi) { + const idsStr = ids.join(","); + MessageAPI.setByIds(idsStr, state); + } + } + + // user logout + function logout() { + return new Promise((resolve, reject) => { + AuthAPI.logout() + .then(() => { + if (timerMessage != null) { + clearInterval(timerMessage); + timerMessage = null; + } + clearUser(); + + localStorage.setItem(TOKEN_KEY, ""); + location.reload(); // 清空路由 + + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); + } + + // remove token + function resetToken() { + // console.log("resetToken"); + return new Promise((resolve) => { + localStorage.setItem(TOKEN_KEY, ""); + resetRouter(); + resolve(); + }); + } + + return { + sessionToken, + user, + account, + msgLatest, + login, + register, + autoLogin, + getUserInfo, + getAccountInfo, + getLatestMessage, + setMessageState, + logout, + clearUser, + resetToken, + }; +}); + +/** + * 用于在组件外部(如在Pinia Store 中)使用 Pinia 提供的 store 实例。 + * 官方文档解释了如何在组件外部使用 Pinia Store: + * https://pinia.vuejs.org/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component + */ +export function useUserStoreHook() { + return useUserStore(store); +} diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..bf0b25b --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,33 @@ +@use "./reset"; + +.app-container { + // padding: 15px; + min-width: 430px; + height: 100%; + margin: 0; + padding: 0; + overflow: auto; +} + +.search-container { + padding: 18px 0 0 20px; + 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); +} + +.table-container > .el-card__header { + padding: calc(var(--el-card-padding) - 8px) var(--el-card-padding); +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32 160 255); + } +} diff --git a/src/styles/login.scss b/src/styles/login.scss new file mode 100644 index 0000000..1d5a873 --- /dev/null +++ b/src/styles/login.scss @@ -0,0 +1,64 @@ +.login-container { + display: flex; + align-items: center; + justify-content: center; + overflow-y: auto; + + .login-card { + width: 400px; + background: transparent; + border: none; + border-radius: 4%; + + @media (width <= 640px) { + width: 340px; + } + + .input-wrapper { + display: flex; + align-items: center; + width: 100%; + } + + .tips-wrapper { + display: flex; + justify-content: space-between; + width: 100%; + + a { + text-decoration: underline; + } + } + + .captcha-image { + height: 48px; + cursor: pointer; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + } + } + + .el-form-item { + background: var(--el-input-bg-color); + border: 1px solid var(--el-border-color); + border-radius: 5px; + } + + .el-input { + .el-input__wrapper { + padding: 0; + background-color: transparent; + box-shadow: none; + + &.is-focus, + &:hover { + box-shadow: none !important; + } + + input:-webkit-autofill { + /* 通过延时渲染背景色变相去除背景颜色 */ + transition: background-color 1000s ease-in-out 0s; + } + } + } +} diff --git a/src/styles/reset.scss b/src/styles/reset.scss new file mode 100644 index 0000000..58233b5 --- /dev/null +++ b/src/styles/reset.scss @@ -0,0 +1,76 @@ +*, +::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: "Microsoft YaHei", "微软雅黑", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", 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 { + // 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 + vertical-align: -0.15em; +} + +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/variables.module.scss b/src/styles/variables.module.scss new file mode 100644 index 0000000..b3f2d52 --- /dev/null +++ b/src/styles/variables.module.scss @@ -0,0 +1,12 @@ +/* stylelint-disable property-no-unknown */ +:export { + sidebar-width: $sidebar-width; + navbar-height: $navbar-height; + tags-view-height: $tags-view-height; + menu-background: $menu-background; + menu-text: $menu-text; + menu-active-text: $menu-active-text; + menu-hover: $menu-hover; + text-high-color: $text-high-color; +} +/* stylelint-enable property-no-unknown */ diff --git a/src/styles/variables.scss b/src/styles/variables.scss new file mode 100644 index 0000000..c569c67 --- /dev/null +++ b/src/styles/variables.scss @@ -0,0 +1,40 @@ +/** 全局SCSS变量 */ + +:root { + --menu-background: #304156; + --menu-text: #bfcbd9; + --menu-active-text: var(--el-menu-active-color); + --menu-hover: #263445; + --sidebar-logo-background: #2d3748; + --text-high-color: #000; // 修复表格 fixed 列被选中后由于透明色导致叠字的 bug + .el-table { + --el-table-current-row-bg-color: rgb(235 243 250); + } +} + +/** 暗黑主题 */ +html.dark { + --menu-background: var(--el-bg-color-overlay); + --menu-text: #fff; + --menu-active-text: var(--el-menu-active-color); + --menu-hover: rgb(0 0 0 / 20%); + --sidebar-logo-background: rgb(0 0 0 / 20%); + --text-high-color: #fff; + + //wangEditer + --w-e-textarea-bg-color: #333; + --w-e-textarea-color: #fff; +} + +$menu-background: var(--menu-background); // 菜单背景色 +$menu-text: var(--menu-text); // 菜单文字颜色 +$menu-active-text: var(--menu-active-text); // 菜单激活文字颜色 +$menu-hover: var(--menu-hover); // 菜单悬停背景色 +$sidebar-logo-background: var(--sidebar-logo-background); // 侧边栏 Logo 背景色 + +$sidebar-width: 210px; // 侧边栏宽度 +$sidebar-width-collapsed: 54px; // 侧边栏收缩宽度 +$navbar-height: 50px; // 导航栏高度 +$tags-view-height: 34px; // TagsView 高度 + +$text-high-color: var(--text-high-color); diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts new file mode 100644 index 0000000..5ecce34 --- /dev/null +++ b/src/types/auto-imports.d.ts @@ -0,0 +1,1053 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: (typeof import("vue"))["EffectScope"]; + const ElMessage: (typeof import("element-plus/es"))["ElMessage"]; + const ElMessageBox: (typeof import("element-plus/es"))["ElMessageBox"]; + const ElNotification: (typeof import("element-plus/es"))["ElNotification"]; + const acceptHMRUpdate: (typeof import("pinia"))["acceptHMRUpdate"]; + const asyncComputed: (typeof import("@vueuse/core"))["asyncComputed"]; + const autoResetRef: (typeof import("@vueuse/core"))["autoResetRef"]; + const computed: (typeof import("vue"))["computed"]; + const computedAsync: (typeof import("@vueuse/core"))["computedAsync"]; + const computedEager: (typeof import("@vueuse/core"))["computedEager"]; + const computedInject: (typeof import("@vueuse/core"))["computedInject"]; + const computedWithControl: (typeof import("@vueuse/core"))["computedWithControl"]; + const controlledComputed: (typeof import("@vueuse/core"))["controlledComputed"]; + const controlledRef: (typeof import("@vueuse/core"))["controlledRef"]; + const createApp: (typeof import("vue"))["createApp"]; + const createEventHook: (typeof import("@vueuse/core"))["createEventHook"]; + const createGlobalState: (typeof import("@vueuse/core"))["createGlobalState"]; + const createInjectionState: (typeof import("@vueuse/core"))["createInjectionState"]; + const createPinia: (typeof import("pinia"))["createPinia"]; + const createReactiveFn: (typeof import("@vueuse/core"))["createReactiveFn"]; + const createReusableTemplate: (typeof import("@vueuse/core"))["createReusableTemplate"]; + const createSharedComposable: (typeof import("@vueuse/core"))["createSharedComposable"]; + const createTemplatePromise: (typeof import("@vueuse/core"))["createTemplatePromise"]; + const createUnrefFn: (typeof import("@vueuse/core"))["createUnrefFn"]; + const customRef: (typeof import("vue"))["customRef"]; + const debouncedRef: (typeof import("@vueuse/core"))["debouncedRef"]; + const debouncedWatch: (typeof import("@vueuse/core"))["debouncedWatch"]; + const defineAsyncComponent: (typeof import("vue"))["defineAsyncComponent"]; + const defineComponent: (typeof import("vue"))["defineComponent"]; + const defineStore: (typeof import("pinia"))["defineStore"]; + const eagerComputed: (typeof import("@vueuse/core"))["eagerComputed"]; + const effectScope: (typeof import("vue"))["effectScope"]; + const extendRef: (typeof import("@vueuse/core"))["extendRef"]; + const getActivePinia: (typeof import("pinia"))["getActivePinia"]; + const getCurrentInstance: (typeof import("vue"))["getCurrentInstance"]; + const getCurrentScope: (typeof import("vue"))["getCurrentScope"]; + const h: (typeof import("vue"))["h"]; + const ignorableWatch: (typeof import("@vueuse/core"))["ignorableWatch"]; + const inject: (typeof import("vue"))["inject"]; + const injectLocal: (typeof import("@vueuse/core"))["injectLocal"]; + const isDefined: (typeof import("@vueuse/core"))["isDefined"]; + const isProxy: (typeof import("vue"))["isProxy"]; + const isReactive: (typeof import("vue"))["isReactive"]; + const isReadonly: (typeof import("vue"))["isReadonly"]; + const isRef: (typeof import("vue"))["isRef"]; + const makeDestructurable: (typeof import("@vueuse/core"))["makeDestructurable"]; + const mapActions: (typeof import("pinia"))["mapActions"]; + const mapGetters: (typeof import("pinia"))["mapGetters"]; + const mapState: (typeof import("pinia"))["mapState"]; + const mapStores: (typeof import("pinia"))["mapStores"]; + const mapWritableState: (typeof import("pinia"))["mapWritableState"]; + const markRaw: (typeof import("vue"))["markRaw"]; + const nextTick: (typeof import("vue"))["nextTick"]; + const onActivated: (typeof import("vue"))["onActivated"]; + const onBeforeMount: (typeof import("vue"))["onBeforeMount"]; + const onBeforeRouteLeave: (typeof import("vue-router"))["onBeforeRouteLeave"]; + const onBeforeRouteUpdate: (typeof import("vue-router"))["onBeforeRouteUpdate"]; + const onBeforeUnmount: (typeof import("vue"))["onBeforeUnmount"]; + const onBeforeUpdate: (typeof import("vue"))["onBeforeUpdate"]; + const onClickOutside: (typeof import("@vueuse/core"))["onClickOutside"]; + const onDeactivated: (typeof import("vue"))["onDeactivated"]; + const onErrorCaptured: (typeof import("vue"))["onErrorCaptured"]; + const onKeyStroke: (typeof import("@vueuse/core"))["onKeyStroke"]; + const onLongPress: (typeof import("@vueuse/core"))["onLongPress"]; + const onMounted: (typeof import("vue"))["onMounted"]; + const onRenderTracked: (typeof import("vue"))["onRenderTracked"]; + const onRenderTriggered: (typeof import("vue"))["onRenderTriggered"]; + const onScopeDispose: (typeof import("vue"))["onScopeDispose"]; + const onServerPrefetch: (typeof import("vue"))["onServerPrefetch"]; + const onStartTyping: (typeof import("@vueuse/core"))["onStartTyping"]; + const onUnmounted: (typeof import("vue"))["onUnmounted"]; + const onUpdated: (typeof import("vue"))["onUpdated"]; + const pausableWatch: (typeof import("@vueuse/core"))["pausableWatch"]; + const provide: (typeof import("vue"))["provide"]; + const provideLocal: (typeof import("@vueuse/core"))["provideLocal"]; + const reactify: (typeof import("@vueuse/core"))["reactify"]; + const reactifyObject: (typeof import("@vueuse/core"))["reactifyObject"]; + const reactive: (typeof import("vue"))["reactive"]; + const reactiveComputed: (typeof import("@vueuse/core"))["reactiveComputed"]; + const reactiveOmit: (typeof import("@vueuse/core"))["reactiveOmit"]; + const reactivePick: (typeof import("@vueuse/core"))["reactivePick"]; + const readonly: (typeof import("vue"))["readonly"]; + const ref: (typeof import("vue"))["ref"]; + const refAutoReset: (typeof import("@vueuse/core"))["refAutoReset"]; + const refDebounced: (typeof import("@vueuse/core"))["refDebounced"]; + const refDefault: (typeof import("@vueuse/core"))["refDefault"]; + const refThrottled: (typeof import("@vueuse/core"))["refThrottled"]; + const refWithControl: (typeof import("@vueuse/core"))["refWithControl"]; + const resolveComponent: (typeof import("vue"))["resolveComponent"]; + const resolveRef: (typeof import("@vueuse/core"))["resolveRef"]; + const resolveUnref: (typeof import("@vueuse/core"))["resolveUnref"]; + const setActivePinia: (typeof import("pinia"))["setActivePinia"]; + const setMapStoreSuffix: (typeof import("pinia"))["setMapStoreSuffix"]; + const shallowReactive: (typeof import("vue"))["shallowReactive"]; + const shallowReadonly: (typeof import("vue"))["shallowReadonly"]; + const shallowRef: (typeof import("vue"))["shallowRef"]; + const storeToRefs: (typeof import("pinia"))["storeToRefs"]; + const syncRef: (typeof import("@vueuse/core"))["syncRef"]; + const syncRefs: (typeof import("@vueuse/core"))["syncRefs"]; + const templateRef: (typeof import("@vueuse/core"))["templateRef"]; + const throttledRef: (typeof import("@vueuse/core"))["throttledRef"]; + const throttledWatch: (typeof import("@vueuse/core"))["throttledWatch"]; + const toRaw: (typeof import("vue"))["toRaw"]; + const toReactive: (typeof import("@vueuse/core"))["toReactive"]; + const toRef: (typeof import("vue"))["toRef"]; + const toRefs: (typeof import("vue"))["toRefs"]; + const toValue: (typeof import("vue"))["toValue"]; + const triggerRef: (typeof import("vue"))["triggerRef"]; + const tryOnBeforeMount: (typeof import("@vueuse/core"))["tryOnBeforeMount"]; + const tryOnBeforeUnmount: (typeof import("@vueuse/core"))["tryOnBeforeUnmount"]; + const tryOnMounted: (typeof import("@vueuse/core"))["tryOnMounted"]; + const tryOnScopeDispose: (typeof import("@vueuse/core"))["tryOnScopeDispose"]; + const tryOnUnmounted: (typeof import("@vueuse/core"))["tryOnUnmounted"]; + const unref: (typeof import("vue"))["unref"]; + const unrefElement: (typeof import("@vueuse/core"))["unrefElement"]; + const until: (typeof import("@vueuse/core"))["until"]; + const useActiveElement: (typeof import("@vueuse/core"))["useActiveElement"]; + const useAnimate: (typeof import("@vueuse/core"))["useAnimate"]; + const useArrayDifference: (typeof import("@vueuse/core"))["useArrayDifference"]; + const useArrayEvery: (typeof import("@vueuse/core"))["useArrayEvery"]; + const useArrayFilter: (typeof import("@vueuse/core"))["useArrayFilter"]; + const useArrayFind: (typeof import("@vueuse/core"))["useArrayFind"]; + const useArrayFindIndex: (typeof import("@vueuse/core"))["useArrayFindIndex"]; + const useArrayFindLast: (typeof import("@vueuse/core"))["useArrayFindLast"]; + const useArrayIncludes: (typeof import("@vueuse/core"))["useArrayIncludes"]; + const useArrayJoin: (typeof import("@vueuse/core"))["useArrayJoin"]; + const useArrayMap: (typeof import("@vueuse/core"))["useArrayMap"]; + const useArrayReduce: (typeof import("@vueuse/core"))["useArrayReduce"]; + const useArraySome: (typeof import("@vueuse/core"))["useArraySome"]; + const useArrayUnique: (typeof import("@vueuse/core"))["useArrayUnique"]; + const useAsyncQueue: (typeof import("@vueuse/core"))["useAsyncQueue"]; + const useAsyncState: (typeof import("@vueuse/core"))["useAsyncState"]; + const useAttrs: (typeof import("vue"))["useAttrs"]; + const useBase64: (typeof import("@vueuse/core"))["useBase64"]; + const useBattery: (typeof import("@vueuse/core"))["useBattery"]; + const useBluetooth: (typeof import("@vueuse/core"))["useBluetooth"]; + const useBreakpoints: (typeof import("@vueuse/core"))["useBreakpoints"]; + const useBroadcastChannel: (typeof import("@vueuse/core"))["useBroadcastChannel"]; + const useBrowserLocation: (typeof import("@vueuse/core"))["useBrowserLocation"]; + const useCached: (typeof import("@vueuse/core"))["useCached"]; + const useClipboard: (typeof import("@vueuse/core"))["useClipboard"]; + const useClipboardItems: (typeof import("@vueuse/core"))["useClipboardItems"]; + const useCloned: (typeof import("@vueuse/core"))["useCloned"]; + const useColorMode: (typeof import("@vueuse/core"))["useColorMode"]; + const useConfirmDialog: (typeof import("@vueuse/core"))["useConfirmDialog"]; + const useCounter: (typeof import("@vueuse/core"))["useCounter"]; + const useCssModule: (typeof import("vue"))["useCssModule"]; + const useCssVar: (typeof import("@vueuse/core"))["useCssVar"]; + const useCssVars: (typeof import("vue"))["useCssVars"]; + const useCurrentElement: (typeof import("@vueuse/core"))["useCurrentElement"]; + const useCycleList: (typeof import("@vueuse/core"))["useCycleList"]; + const useDark: (typeof import("@vueuse/core"))["useDark"]; + const useDateFormat: (typeof import("@vueuse/core"))["useDateFormat"]; + const useDebounce: (typeof import("@vueuse/core"))["useDebounce"]; + const useDebounceFn: (typeof import("@vueuse/core"))["useDebounceFn"]; + const useDebouncedRefHistory: (typeof import("@vueuse/core"))["useDebouncedRefHistory"]; + const useDeviceMotion: (typeof import("@vueuse/core"))["useDeviceMotion"]; + const useDeviceOrientation: (typeof import("@vueuse/core"))["useDeviceOrientation"]; + const useDevicePixelRatio: (typeof import("@vueuse/core"))["useDevicePixelRatio"]; + const useDevicesList: (typeof import("@vueuse/core"))["useDevicesList"]; + const useDisplayMedia: (typeof import("@vueuse/core"))["useDisplayMedia"]; + const useDocumentVisibility: (typeof import("@vueuse/core"))["useDocumentVisibility"]; + const useDraggable: (typeof import("@vueuse/core"))["useDraggable"]; + const useDropZone: (typeof import("@vueuse/core"))["useDropZone"]; + const useElementBounding: (typeof import("@vueuse/core"))["useElementBounding"]; + const useElementByPoint: (typeof import("@vueuse/core"))["useElementByPoint"]; + const useElementHover: (typeof import("@vueuse/core"))["useElementHover"]; + const useElementSize: (typeof import("@vueuse/core"))["useElementSize"]; + const useElementVisibility: (typeof import("@vueuse/core"))["useElementVisibility"]; + const useEventBus: (typeof import("@vueuse/core"))["useEventBus"]; + const useEventListener: (typeof import("@vueuse/core"))["useEventListener"]; + const useEventSource: (typeof import("@vueuse/core"))["useEventSource"]; + const useEyeDropper: (typeof import("@vueuse/core"))["useEyeDropper"]; + const useFavicon: (typeof import("@vueuse/core"))["useFavicon"]; + const useFetch: (typeof import("@vueuse/core"))["useFetch"]; + const useFileDialog: (typeof import("@vueuse/core"))["useFileDialog"]; + const useFileSystemAccess: (typeof import("@vueuse/core"))["useFileSystemAccess"]; + const useFocus: (typeof import("@vueuse/core"))["useFocus"]; + const useFocusWithin: (typeof import("@vueuse/core"))["useFocusWithin"]; + const useFps: (typeof import("@vueuse/core"))["useFps"]; + const useFullscreen: (typeof import("@vueuse/core"))["useFullscreen"]; + const useGamepad: (typeof import("@vueuse/core"))["useGamepad"]; + const useGeolocation: (typeof import("@vueuse/core"))["useGeolocation"]; + const useI18n: (typeof import("vue-i18n"))["useI18n"]; + const useIdle: (typeof import("@vueuse/core"))["useIdle"]; + const useImage: (typeof import("@vueuse/core"))["useImage"]; + const useInfiniteScroll: (typeof import("@vueuse/core"))["useInfiniteScroll"]; + const useIntersectionObserver: (typeof import("@vueuse/core"))["useIntersectionObserver"]; + const useInterval: (typeof import("@vueuse/core"))["useInterval"]; + const useIntervalFn: (typeof import("@vueuse/core"))["useIntervalFn"]; + const useKeyModifier: (typeof import("@vueuse/core"))["useKeyModifier"]; + const useLastChanged: (typeof import("@vueuse/core"))["useLastChanged"]; + const useLink: (typeof import("vue-router"))["useLink"]; + const useLocalStorage: (typeof import("@vueuse/core"))["useLocalStorage"]; + const useMagicKeys: (typeof import("@vueuse/core"))["useMagicKeys"]; + const useManualRefHistory: (typeof import("@vueuse/core"))["useManualRefHistory"]; + const useMediaControls: (typeof import("@vueuse/core"))["useMediaControls"]; + const useMediaQuery: (typeof import("@vueuse/core"))["useMediaQuery"]; + const useMemoize: (typeof import("@vueuse/core"))["useMemoize"]; + const useMemory: (typeof import("@vueuse/core"))["useMemory"]; + const useMounted: (typeof import("@vueuse/core"))["useMounted"]; + const useMouse: (typeof import("@vueuse/core"))["useMouse"]; + const useMouseInElement: (typeof import("@vueuse/core"))["useMouseInElement"]; + const useMousePressed: (typeof import("@vueuse/core"))["useMousePressed"]; + const useMutationObserver: (typeof import("@vueuse/core"))["useMutationObserver"]; + const useNavigatorLanguage: (typeof import("@vueuse/core"))["useNavigatorLanguage"]; + const useNetwork: (typeof import("@vueuse/core"))["useNetwork"]; + const useNow: (typeof import("@vueuse/core"))["useNow"]; + const useObjectUrl: (typeof import("@vueuse/core"))["useObjectUrl"]; + const useOffsetPagination: (typeof import("@vueuse/core"))["useOffsetPagination"]; + const useOnline: (typeof import("@vueuse/core"))["useOnline"]; + const usePageLeave: (typeof import("@vueuse/core"))["usePageLeave"]; + const useParallax: (typeof import("@vueuse/core"))["useParallax"]; + const useParentElement: (typeof import("@vueuse/core"))["useParentElement"]; + const usePerformanceObserver: (typeof import("@vueuse/core"))["usePerformanceObserver"]; + const usePermission: (typeof import("@vueuse/core"))["usePermission"]; + const usePointer: (typeof import("@vueuse/core"))["usePointer"]; + const usePointerLock: (typeof import("@vueuse/core"))["usePointerLock"]; + const usePointerSwipe: (typeof import("@vueuse/core"))["usePointerSwipe"]; + const usePreferredColorScheme: (typeof import("@vueuse/core"))["usePreferredColorScheme"]; + const usePreferredContrast: (typeof import("@vueuse/core"))["usePreferredContrast"]; + const usePreferredDark: (typeof import("@vueuse/core"))["usePreferredDark"]; + const usePreferredLanguages: (typeof import("@vueuse/core"))["usePreferredLanguages"]; + const usePreferredReducedMotion: (typeof import("@vueuse/core"))["usePreferredReducedMotion"]; + const usePrevious: (typeof import("@vueuse/core"))["usePrevious"]; + const useRafFn: (typeof import("@vueuse/core"))["useRafFn"]; + const useRefHistory: (typeof import("@vueuse/core"))["useRefHistory"]; + const useResizeObserver: (typeof import("@vueuse/core"))["useResizeObserver"]; + const useRoute: (typeof import("vue-router"))["useRoute"]; + const useRouter: (typeof import("vue-router"))["useRouter"]; + const useScreenOrientation: (typeof import("@vueuse/core"))["useScreenOrientation"]; + const useScreenSafeArea: (typeof import("@vueuse/core"))["useScreenSafeArea"]; + const useScriptTag: (typeof import("@vueuse/core"))["useScriptTag"]; + const useScroll: (typeof import("@vueuse/core"))["useScroll"]; + const useScrollLock: (typeof import("@vueuse/core"))["useScrollLock"]; + const useSessionStorage: (typeof import("@vueuse/core"))["useSessionStorage"]; + const useShare: (typeof import("@vueuse/core"))["useShare"]; + const useSlots: (typeof import("vue"))["useSlots"]; + const useSorted: (typeof import("@vueuse/core"))["useSorted"]; + const useSpeechRecognition: (typeof import("@vueuse/core"))["useSpeechRecognition"]; + const useSpeechSynthesis: (typeof import("@vueuse/core"))["useSpeechSynthesis"]; + const useStepper: (typeof import("@vueuse/core"))["useStepper"]; + const useStorage: (typeof import("@vueuse/core"))["useStorage"]; + const useStorageAsync: (typeof import("@vueuse/core"))["useStorageAsync"]; + const useStyleTag: (typeof import("@vueuse/core"))["useStyleTag"]; + const useSupported: (typeof import("@vueuse/core"))["useSupported"]; + const useSwipe: (typeof import("@vueuse/core"))["useSwipe"]; + const 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, + ExtractDefaultPropTypes, + ExtractPropTypes, + ExtractPublicPropTypes, + InjectionKey, + PropType, + Ref, + VNode, + WritableComputedRef, + } from "vue"; + import("vue"); +} +// for vue template auto import +import { UnwrapRef } from "vue"; +declare module "vue" { + interface GlobalComponents {} + interface ComponentCustomProperties { + readonly EffectScope: UnwrapRef<(typeof import("vue"))["EffectScope"]>; + readonly ElMessage: UnwrapRef< + (typeof import("element-plus/es"))["ElMessage"] + >; + readonly ElMessageBox: UnwrapRef< + (typeof import("element-plus/es"))["ElMessageBox"] + >; + readonly ElNotification: UnwrapRef< + (typeof import("element-plus/es"))["ElNotification"] + >; + readonly acceptHMRUpdate: UnwrapRef< + (typeof import("pinia"))["acceptHMRUpdate"] + >; + readonly asyncComputed: UnwrapRef< + (typeof import("@vueuse/core"))["asyncComputed"] + >; + readonly autoResetRef: UnwrapRef< + (typeof import("@vueuse/core"))["autoResetRef"] + >; + readonly computed: UnwrapRef<(typeof import("vue"))["computed"]>; + readonly computedAsync: UnwrapRef< + (typeof import("@vueuse/core"))["computedAsync"] + >; + readonly computedEager: UnwrapRef< + (typeof import("@vueuse/core"))["computedEager"] + >; + readonly computedInject: UnwrapRef< + (typeof import("@vueuse/core"))["computedInject"] + >; + readonly computedWithControl: UnwrapRef< + (typeof import("@vueuse/core"))["computedWithControl"] + >; + readonly controlledComputed: UnwrapRef< + (typeof import("@vueuse/core"))["controlledComputed"] + >; + readonly controlledRef: UnwrapRef< + (typeof import("@vueuse/core"))["controlledRef"] + >; + readonly createApp: UnwrapRef<(typeof import("vue"))["createApp"]>; + readonly createEventHook: UnwrapRef< + (typeof import("@vueuse/core"))["createEventHook"] + >; + readonly createGlobalState: UnwrapRef< + (typeof import("@vueuse/core"))["createGlobalState"] + >; + readonly createInjectionState: UnwrapRef< + (typeof import("@vueuse/core"))["createInjectionState"] + >; + readonly createPinia: UnwrapRef<(typeof import("pinia"))["createPinia"]>; + readonly createReactiveFn: UnwrapRef< + (typeof import("@vueuse/core"))["createReactiveFn"] + >; + readonly createReusableTemplate: UnwrapRef< + (typeof import("@vueuse/core"))["createReusableTemplate"] + >; + readonly createSharedComposable: UnwrapRef< + (typeof import("@vueuse/core"))["createSharedComposable"] + >; + readonly createTemplatePromise: UnwrapRef< + (typeof import("@vueuse/core"))["createTemplatePromise"] + >; + readonly createUnrefFn: UnwrapRef< + (typeof import("@vueuse/core"))["createUnrefFn"] + >; + readonly customRef: UnwrapRef<(typeof import("vue"))["customRef"]>; + readonly debouncedRef: UnwrapRef< + (typeof import("@vueuse/core"))["debouncedRef"] + >; + readonly debouncedWatch: UnwrapRef< + (typeof import("@vueuse/core"))["debouncedWatch"] + >; + readonly defineAsyncComponent: UnwrapRef< + (typeof import("vue"))["defineAsyncComponent"] + >; + readonly defineComponent: UnwrapRef< + (typeof import("vue"))["defineComponent"] + >; + readonly defineStore: UnwrapRef<(typeof import("pinia"))["defineStore"]>; + readonly eagerComputed: UnwrapRef< + (typeof import("@vueuse/core"))["eagerComputed"] + >; + readonly effectScope: UnwrapRef<(typeof import("vue"))["effectScope"]>; + readonly extendRef: UnwrapRef<(typeof import("@vueuse/core"))["extendRef"]>; + readonly getActivePinia: UnwrapRef< + (typeof import("pinia"))["getActivePinia"] + >; + readonly getCurrentInstance: UnwrapRef< + (typeof import("vue"))["getCurrentInstance"] + >; + readonly getCurrentScope: UnwrapRef< + (typeof import("vue"))["getCurrentScope"] + >; + readonly h: UnwrapRef<(typeof import("vue"))["h"]>; + readonly ignorableWatch: UnwrapRef< + (typeof import("@vueuse/core"))["ignorableWatch"] + >; + readonly inject: UnwrapRef<(typeof import("vue"))["inject"]>; + readonly injectLocal: UnwrapRef< + (typeof import("@vueuse/core"))["injectLocal"] + >; + readonly isDefined: UnwrapRef<(typeof import("@vueuse/core"))["isDefined"]>; + readonly isProxy: UnwrapRef<(typeof import("vue"))["isProxy"]>; + readonly isReactive: UnwrapRef<(typeof import("vue"))["isReactive"]>; + readonly isReadonly: UnwrapRef<(typeof import("vue"))["isReadonly"]>; + readonly isRef: UnwrapRef<(typeof import("vue"))["isRef"]>; + readonly makeDestructurable: UnwrapRef< + (typeof import("@vueuse/core"))["makeDestructurable"] + >; + readonly mapActions: UnwrapRef<(typeof import("pinia"))["mapActions"]>; + readonly mapGetters: UnwrapRef<(typeof import("pinia"))["mapGetters"]>; + readonly mapState: UnwrapRef<(typeof import("pinia"))["mapState"]>; + readonly mapStores: UnwrapRef<(typeof import("pinia"))["mapStores"]>; + readonly mapWritableState: UnwrapRef< + (typeof import("pinia"))["mapWritableState"] + >; + readonly markRaw: UnwrapRef<(typeof import("vue"))["markRaw"]>; + readonly nextTick: UnwrapRef<(typeof import("vue"))["nextTick"]>; + readonly onActivated: UnwrapRef<(typeof import("vue"))["onActivated"]>; + readonly onBeforeMount: UnwrapRef<(typeof import("vue"))["onBeforeMount"]>; + readonly onBeforeRouteLeave: UnwrapRef< + (typeof import("vue-router"))["onBeforeRouteLeave"] + >; + readonly onBeforeRouteUpdate: UnwrapRef< + (typeof import("vue-router"))["onBeforeRouteUpdate"] + >; + readonly onBeforeUnmount: UnwrapRef< + (typeof import("vue"))["onBeforeUnmount"] + >; + readonly onBeforeUpdate: UnwrapRef< + (typeof import("vue"))["onBeforeUpdate"] + >; + readonly onClickOutside: UnwrapRef< + (typeof import("@vueuse/core"))["onClickOutside"] + >; + readonly onDeactivated: UnwrapRef<(typeof import("vue"))["onDeactivated"]>; + readonly onErrorCaptured: UnwrapRef< + (typeof import("vue"))["onErrorCaptured"] + >; + readonly onKeyStroke: UnwrapRef< + (typeof import("@vueuse/core"))["onKeyStroke"] + >; + readonly onLongPress: UnwrapRef< + (typeof import("@vueuse/core"))["onLongPress"] + >; + readonly onMounted: UnwrapRef<(typeof import("vue"))["onMounted"]>; + readonly onRenderTracked: UnwrapRef< + (typeof import("vue"))["onRenderTracked"] + >; + readonly onRenderTriggered: UnwrapRef< + (typeof import("vue"))["onRenderTriggered"] + >; + readonly onScopeDispose: UnwrapRef< + (typeof import("vue"))["onScopeDispose"] + >; + readonly onServerPrefetch: UnwrapRef< + (typeof import("vue"))["onServerPrefetch"] + >; + readonly onStartTyping: UnwrapRef< + (typeof import("@vueuse/core"))["onStartTyping"] + >; + readonly onUnmounted: UnwrapRef<(typeof import("vue"))["onUnmounted"]>; + readonly onUpdated: UnwrapRef<(typeof import("vue"))["onUpdated"]>; + readonly pausableWatch: UnwrapRef< + (typeof import("@vueuse/core"))["pausableWatch"] + >; + readonly provide: UnwrapRef<(typeof import("vue"))["provide"]>; + readonly provideLocal: UnwrapRef< + (typeof import("@vueuse/core"))["provideLocal"] + >; + readonly reactify: UnwrapRef<(typeof import("@vueuse/core"))["reactify"]>; + readonly reactifyObject: UnwrapRef< + (typeof import("@vueuse/core"))["reactifyObject"] + >; + readonly reactive: UnwrapRef<(typeof import("vue"))["reactive"]>; + readonly reactiveComputed: UnwrapRef< + (typeof import("@vueuse/core"))["reactiveComputed"] + >; + readonly reactiveOmit: UnwrapRef< + (typeof import("@vueuse/core"))["reactiveOmit"] + >; + readonly reactivePick: UnwrapRef< + (typeof import("@vueuse/core"))["reactivePick"] + >; + readonly readonly: UnwrapRef<(typeof import("vue"))["readonly"]>; + readonly ref: UnwrapRef<(typeof import("vue"))["ref"]>; + readonly refAutoReset: UnwrapRef< + (typeof import("@vueuse/core"))["refAutoReset"] + >; + readonly refDebounced: UnwrapRef< + (typeof import("@vueuse/core"))["refDebounced"] + >; + readonly refDefault: UnwrapRef< + (typeof import("@vueuse/core"))["refDefault"] + >; + readonly refThrottled: UnwrapRef< + (typeof import("@vueuse/core"))["refThrottled"] + >; + readonly refWithControl: UnwrapRef< + (typeof import("@vueuse/core"))["refWithControl"] + >; + readonly resolveComponent: UnwrapRef< + (typeof import("vue"))["resolveComponent"] + >; + readonly resolveRef: UnwrapRef< + (typeof import("@vueuse/core"))["resolveRef"] + >; + readonly resolveUnref: UnwrapRef< + (typeof import("@vueuse/core"))["resolveUnref"] + >; + readonly setActivePinia: UnwrapRef< + (typeof import("pinia"))["setActivePinia"] + >; + readonly setMapStoreSuffix: UnwrapRef< + (typeof import("pinia"))["setMapStoreSuffix"] + >; + readonly shallowReactive: UnwrapRef< + (typeof import("vue"))["shallowReactive"] + >; + readonly shallowReadonly: UnwrapRef< + (typeof import("vue"))["shallowReadonly"] + >; + readonly shallowRef: UnwrapRef<(typeof import("vue"))["shallowRef"]>; + readonly storeToRefs: UnwrapRef<(typeof import("pinia"))["storeToRefs"]>; + readonly syncRef: UnwrapRef<(typeof import("@vueuse/core"))["syncRef"]>; + readonly syncRefs: UnwrapRef<(typeof import("@vueuse/core"))["syncRefs"]>; + readonly templateRef: UnwrapRef< + (typeof import("@vueuse/core"))["templateRef"] + >; + readonly throttledRef: UnwrapRef< + (typeof import("@vueuse/core"))["throttledRef"] + >; + readonly throttledWatch: UnwrapRef< + (typeof import("@vueuse/core"))["throttledWatch"] + >; + readonly toRaw: UnwrapRef<(typeof import("vue"))["toRaw"]>; + readonly toReactive: UnwrapRef< + (typeof import("@vueuse/core"))["toReactive"] + >; + readonly toRef: UnwrapRef<(typeof import("vue"))["toRef"]>; + readonly toRefs: UnwrapRef<(typeof import("vue"))["toRefs"]>; + readonly toValue: UnwrapRef<(typeof import("vue"))["toValue"]>; + readonly triggerRef: UnwrapRef<(typeof import("vue"))["triggerRef"]>; + readonly tryOnBeforeMount: UnwrapRef< + (typeof import("@vueuse/core"))["tryOnBeforeMount"] + >; + readonly tryOnBeforeUnmount: UnwrapRef< + (typeof import("@vueuse/core"))["tryOnBeforeUnmount"] + >; + readonly tryOnMounted: UnwrapRef< + (typeof import("@vueuse/core"))["tryOnMounted"] + >; + readonly tryOnScopeDispose: UnwrapRef< + (typeof import("@vueuse/core"))["tryOnScopeDispose"] + >; + readonly tryOnUnmounted: UnwrapRef< + (typeof import("@vueuse/core"))["tryOnUnmounted"] + >; + readonly unref: UnwrapRef<(typeof import("vue"))["unref"]>; + readonly unrefElement: UnwrapRef< + (typeof import("@vueuse/core"))["unrefElement"] + >; + readonly until: UnwrapRef<(typeof import("@vueuse/core"))["until"]>; + readonly useActiveElement: UnwrapRef< + (typeof import("@vueuse/core"))["useActiveElement"] + >; + readonly useAnimate: UnwrapRef< + (typeof import("@vueuse/core"))["useAnimate"] + >; + readonly useArrayDifference: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayDifference"] + >; + readonly useArrayEvery: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayEvery"] + >; + readonly useArrayFilter: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayFilter"] + >; + readonly useArrayFind: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayFind"] + >; + readonly useArrayFindIndex: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayFindIndex"] + >; + readonly useArrayFindLast: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayFindLast"] + >; + readonly useArrayIncludes: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayIncludes"] + >; + readonly useArrayJoin: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayJoin"] + >; + readonly useArrayMap: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayMap"] + >; + readonly useArrayReduce: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayReduce"] + >; + readonly useArraySome: UnwrapRef< + (typeof import("@vueuse/core"))["useArraySome"] + >; + readonly useArrayUnique: UnwrapRef< + (typeof import("@vueuse/core"))["useArrayUnique"] + >; + readonly useAsyncQueue: UnwrapRef< + (typeof import("@vueuse/core"))["useAsyncQueue"] + >; + readonly useAsyncState: UnwrapRef< + (typeof import("@vueuse/core"))["useAsyncState"] + >; + readonly useAttrs: UnwrapRef<(typeof import("vue"))["useAttrs"]>; + readonly useBase64: UnwrapRef<(typeof import("@vueuse/core"))["useBase64"]>; + readonly useBattery: UnwrapRef< + (typeof import("@vueuse/core"))["useBattery"] + >; + readonly useBluetooth: UnwrapRef< + (typeof import("@vueuse/core"))["useBluetooth"] + >; + readonly useBreakpoints: UnwrapRef< + (typeof import("@vueuse/core"))["useBreakpoints"] + >; + readonly useBroadcastChannel: UnwrapRef< + (typeof import("@vueuse/core"))["useBroadcastChannel"] + >; + readonly useBrowserLocation: UnwrapRef< + (typeof import("@vueuse/core"))["useBrowserLocation"] + >; + readonly useCached: UnwrapRef<(typeof import("@vueuse/core"))["useCached"]>; + readonly useClipboard: UnwrapRef< + (typeof import("@vueuse/core"))["useClipboard"] + >; + readonly useClipboardItems: UnwrapRef< + (typeof import("@vueuse/core"))["useClipboardItems"] + >; + readonly useCloned: UnwrapRef<(typeof import("@vueuse/core"))["useCloned"]>; + readonly useColorMode: UnwrapRef< + (typeof import("@vueuse/core"))["useColorMode"] + >; + readonly useConfirmDialog: UnwrapRef< + (typeof import("@vueuse/core"))["useConfirmDialog"] + >; + readonly useCounter: UnwrapRef< + (typeof import("@vueuse/core"))["useCounter"] + >; + readonly useCssModule: UnwrapRef<(typeof import("vue"))["useCssModule"]>; + readonly useCssVar: UnwrapRef<(typeof import("@vueuse/core"))["useCssVar"]>; + readonly useCssVars: UnwrapRef<(typeof import("vue"))["useCssVars"]>; + readonly useCurrentElement: UnwrapRef< + (typeof import("@vueuse/core"))["useCurrentElement"] + >; + readonly useCycleList: UnwrapRef< + (typeof import("@vueuse/core"))["useCycleList"] + >; + readonly useDark: UnwrapRef<(typeof import("@vueuse/core"))["useDark"]>; + readonly useDateFormat: UnwrapRef< + (typeof import("@vueuse/core"))["useDateFormat"] + >; + readonly useDebounce: UnwrapRef< + (typeof import("@vueuse/core"))["useDebounce"] + >; + readonly useDebounceFn: UnwrapRef< + (typeof import("@vueuse/core"))["useDebounceFn"] + >; + readonly useDebouncedRefHistory: UnwrapRef< + (typeof import("@vueuse/core"))["useDebouncedRefHistory"] + >; + readonly useDeviceMotion: UnwrapRef< + (typeof import("@vueuse/core"))["useDeviceMotion"] + >; + readonly useDeviceOrientation: UnwrapRef< + (typeof import("@vueuse/core"))["useDeviceOrientation"] + >; + readonly useDevicePixelRatio: UnwrapRef< + (typeof import("@vueuse/core"))["useDevicePixelRatio"] + >; + readonly useDevicesList: UnwrapRef< + (typeof import("@vueuse/core"))["useDevicesList"] + >; + readonly useDisplayMedia: UnwrapRef< + (typeof import("@vueuse/core"))["useDisplayMedia"] + >; + readonly useDocumentVisibility: UnwrapRef< + (typeof import("@vueuse/core"))["useDocumentVisibility"] + >; + readonly useDraggable: UnwrapRef< + (typeof import("@vueuse/core"))["useDraggable"] + >; + readonly useDropZone: UnwrapRef< + (typeof import("@vueuse/core"))["useDropZone"] + >; + readonly useElementBounding: UnwrapRef< + (typeof import("@vueuse/core"))["useElementBounding"] + >; + readonly useElementByPoint: UnwrapRef< + (typeof import("@vueuse/core"))["useElementByPoint"] + >; + readonly useElementHover: UnwrapRef< + (typeof import("@vueuse/core"))["useElementHover"] + >; + readonly useElementSize: UnwrapRef< + (typeof import("@vueuse/core"))["useElementSize"] + >; + readonly useElementVisibility: UnwrapRef< + (typeof import("@vueuse/core"))["useElementVisibility"] + >; + readonly useEventBus: UnwrapRef< + (typeof import("@vueuse/core"))["useEventBus"] + >; + readonly useEventListener: UnwrapRef< + (typeof import("@vueuse/core"))["useEventListener"] + >; + readonly useEventSource: UnwrapRef< + (typeof import("@vueuse/core"))["useEventSource"] + >; + readonly useEyeDropper: UnwrapRef< + (typeof import("@vueuse/core"))["useEyeDropper"] + >; + readonly useFavicon: UnwrapRef< + (typeof import("@vueuse/core"))["useFavicon"] + >; + readonly useFetch: UnwrapRef<(typeof import("@vueuse/core"))["useFetch"]>; + readonly useFileDialog: UnwrapRef< + (typeof import("@vueuse/core"))["useFileDialog"] + >; + readonly useFileSystemAccess: UnwrapRef< + (typeof import("@vueuse/core"))["useFileSystemAccess"] + >; + readonly useFocus: UnwrapRef<(typeof import("@vueuse/core"))["useFocus"]>; + readonly useFocusWithin: UnwrapRef< + (typeof import("@vueuse/core"))["useFocusWithin"] + >; + readonly useFps: UnwrapRef<(typeof import("@vueuse/core"))["useFps"]>; + readonly useFullscreen: UnwrapRef< + (typeof import("@vueuse/core"))["useFullscreen"] + >; + readonly useGamepad: UnwrapRef< + (typeof import("@vueuse/core"))["useGamepad"] + >; + readonly useGeolocation: UnwrapRef< + (typeof import("@vueuse/core"))["useGeolocation"] + >; + readonly useI18n: UnwrapRef<(typeof import("vue-i18n"))["useI18n"]>; + readonly useIdle: UnwrapRef<(typeof import("@vueuse/core"))["useIdle"]>; + readonly useImage: UnwrapRef<(typeof import("@vueuse/core"))["useImage"]>; + readonly useInfiniteScroll: UnwrapRef< + (typeof import("@vueuse/core"))["useInfiniteScroll"] + >; + readonly useIntersectionObserver: UnwrapRef< + (typeof import("@vueuse/core"))["useIntersectionObserver"] + >; + readonly useInterval: UnwrapRef< + (typeof import("@vueuse/core"))["useInterval"] + >; + readonly useIntervalFn: UnwrapRef< + (typeof import("@vueuse/core"))["useIntervalFn"] + >; + readonly useKeyModifier: UnwrapRef< + (typeof import("@vueuse/core"))["useKeyModifier"] + >; + readonly useLastChanged: UnwrapRef< + (typeof import("@vueuse/core"))["useLastChanged"] + >; + readonly useLink: UnwrapRef<(typeof import("vue-router"))["useLink"]>; + readonly useLocalStorage: UnwrapRef< + (typeof import("@vueuse/core"))["useLocalStorage"] + >; + readonly useMagicKeys: UnwrapRef< + (typeof import("@vueuse/core"))["useMagicKeys"] + >; + readonly useManualRefHistory: UnwrapRef< + (typeof import("@vueuse/core"))["useManualRefHistory"] + >; + readonly useMediaControls: UnwrapRef< + (typeof import("@vueuse/core"))["useMediaControls"] + >; + readonly useMediaQuery: UnwrapRef< + (typeof import("@vueuse/core"))["useMediaQuery"] + >; + readonly useMemoize: UnwrapRef< + (typeof import("@vueuse/core"))["useMemoize"] + >; + readonly useMemory: UnwrapRef<(typeof import("@vueuse/core"))["useMemory"]>; + readonly useMounted: UnwrapRef< + (typeof import("@vueuse/core"))["useMounted"] + >; + readonly useMouse: UnwrapRef<(typeof import("@vueuse/core"))["useMouse"]>; + readonly useMouseInElement: UnwrapRef< + (typeof import("@vueuse/core"))["useMouseInElement"] + >; + readonly useMousePressed: UnwrapRef< + (typeof import("@vueuse/core"))["useMousePressed"] + >; + readonly useMutationObserver: UnwrapRef< + (typeof import("@vueuse/core"))["useMutationObserver"] + >; + readonly useNavigatorLanguage: UnwrapRef< + (typeof import("@vueuse/core"))["useNavigatorLanguage"] + >; + readonly useNetwork: UnwrapRef< + (typeof import("@vueuse/core"))["useNetwork"] + >; + readonly useNow: UnwrapRef<(typeof import("@vueuse/core"))["useNow"]>; + readonly useObjectUrl: UnwrapRef< + (typeof import("@vueuse/core"))["useObjectUrl"] + >; + readonly useOffsetPagination: UnwrapRef< + (typeof import("@vueuse/core"))["useOffsetPagination"] + >; + readonly useOnline: UnwrapRef<(typeof import("@vueuse/core"))["useOnline"]>; + readonly usePageLeave: UnwrapRef< + (typeof import("@vueuse/core"))["usePageLeave"] + >; + readonly useParallax: UnwrapRef< + (typeof import("@vueuse/core"))["useParallax"] + >; + readonly useParentElement: UnwrapRef< + (typeof import("@vueuse/core"))["useParentElement"] + >; + readonly usePerformanceObserver: UnwrapRef< + (typeof import("@vueuse/core"))["usePerformanceObserver"] + >; + readonly usePermission: UnwrapRef< + (typeof import("@vueuse/core"))["usePermission"] + >; + readonly usePointer: UnwrapRef< + (typeof import("@vueuse/core"))["usePointer"] + >; + readonly usePointerLock: UnwrapRef< + (typeof import("@vueuse/core"))["usePointerLock"] + >; + readonly usePointerSwipe: UnwrapRef< + (typeof import("@vueuse/core"))["usePointerSwipe"] + >; + readonly usePreferredColorScheme: UnwrapRef< + (typeof import("@vueuse/core"))["usePreferredColorScheme"] + >; + readonly usePreferredContrast: UnwrapRef< + (typeof import("@vueuse/core"))["usePreferredContrast"] + >; + readonly usePreferredDark: UnwrapRef< + (typeof import("@vueuse/core"))["usePreferredDark"] + >; + readonly usePreferredLanguages: UnwrapRef< + (typeof import("@vueuse/core"))["usePreferredLanguages"] + >; + readonly usePreferredReducedMotion: UnwrapRef< + (typeof import("@vueuse/core"))["usePreferredReducedMotion"] + >; + readonly usePrevious: UnwrapRef< + (typeof import("@vueuse/core"))["usePrevious"] + >; + readonly useRafFn: UnwrapRef<(typeof import("@vueuse/core"))["useRafFn"]>; + readonly useRefHistory: UnwrapRef< + (typeof import("@vueuse/core"))["useRefHistory"] + >; + readonly useResizeObserver: UnwrapRef< + (typeof import("@vueuse/core"))["useResizeObserver"] + >; + readonly useRoute: UnwrapRef<(typeof import("vue-router"))["useRoute"]>; + readonly useRouter: UnwrapRef<(typeof import("vue-router"))["useRouter"]>; + readonly useScreenOrientation: UnwrapRef< + (typeof import("@vueuse/core"))["useScreenOrientation"] + >; + readonly useScreenSafeArea: UnwrapRef< + (typeof import("@vueuse/core"))["useScreenSafeArea"] + >; + readonly useScriptTag: UnwrapRef< + (typeof import("@vueuse/core"))["useScriptTag"] + >; + readonly useScroll: UnwrapRef<(typeof import("@vueuse/core"))["useScroll"]>; + readonly useScrollLock: UnwrapRef< + (typeof import("@vueuse/core"))["useScrollLock"] + >; + readonly useSessionStorage: UnwrapRef< + (typeof import("@vueuse/core"))["useSessionStorage"] + >; + readonly useShare: UnwrapRef<(typeof import("@vueuse/core"))["useShare"]>; + readonly useSlots: UnwrapRef<(typeof import("vue"))["useSlots"]>; + readonly useSorted: UnwrapRef<(typeof import("@vueuse/core"))["useSorted"]>; + readonly useSpeechRecognition: UnwrapRef< + (typeof import("@vueuse/core"))["useSpeechRecognition"] + >; + readonly useSpeechSynthesis: UnwrapRef< + (typeof import("@vueuse/core"))["useSpeechSynthesis"] + >; + readonly useStepper: UnwrapRef< + (typeof import("@vueuse/core"))["useStepper"] + >; + readonly useStorage: UnwrapRef< + (typeof import("@vueuse/core"))["useStorage"] + >; + readonly useStorageAsync: UnwrapRef< + (typeof import("@vueuse/core"))["useStorageAsync"] + >; + readonly useStyleTag: UnwrapRef< + (typeof import("@vueuse/core"))["useStyleTag"] + >; + readonly useSupported: UnwrapRef< + (typeof import("@vueuse/core"))["useSupported"] + >; + readonly useSwipe: UnwrapRef<(typeof import("@vueuse/core"))["useSwipe"]>; + readonly useTemplateRefsList: UnwrapRef< + (typeof import("@vueuse/core"))["useTemplateRefsList"] + >; + readonly useTextDirection: UnwrapRef< + (typeof import("@vueuse/core"))["useTextDirection"] + >; + readonly useTextSelection: UnwrapRef< + (typeof import("@vueuse/core"))["useTextSelection"] + >; + readonly useTextareaAutosize: UnwrapRef< + (typeof import("@vueuse/core"))["useTextareaAutosize"] + >; + readonly useThrottle: UnwrapRef< + (typeof import("@vueuse/core"))["useThrottle"] + >; + readonly useThrottleFn: UnwrapRef< + (typeof import("@vueuse/core"))["useThrottleFn"] + >; + readonly useThrottledRefHistory: UnwrapRef< + (typeof import("@vueuse/core"))["useThrottledRefHistory"] + >; + readonly useTimeAgo: UnwrapRef< + (typeof import("@vueuse/core"))["useTimeAgo"] + >; + readonly useTimeout: UnwrapRef< + (typeof import("@vueuse/core"))["useTimeout"] + >; + readonly useTimeoutFn: UnwrapRef< + (typeof import("@vueuse/core"))["useTimeoutFn"] + >; + readonly useTimeoutPoll: UnwrapRef< + (typeof import("@vueuse/core"))["useTimeoutPoll"] + >; + readonly useTimestamp: UnwrapRef< + (typeof import("@vueuse/core"))["useTimestamp"] + >; + readonly useTitle: UnwrapRef<(typeof import("@vueuse/core"))["useTitle"]>; + readonly useToNumber: UnwrapRef< + (typeof import("@vueuse/core"))["useToNumber"] + >; + readonly useToString: UnwrapRef< + (typeof import("@vueuse/core"))["useToString"] + >; + readonly useToggle: UnwrapRef<(typeof import("@vueuse/core"))["useToggle"]>; + readonly useTransition: UnwrapRef< + (typeof import("@vueuse/core"))["useTransition"] + >; + readonly useUrlSearchParams: UnwrapRef< + (typeof import("@vueuse/core"))["useUrlSearchParams"] + >; + readonly useUserMedia: UnwrapRef< + (typeof import("@vueuse/core"))["useUserMedia"] + >; + readonly useVModel: UnwrapRef<(typeof import("@vueuse/core"))["useVModel"]>; + readonly useVModels: UnwrapRef< + (typeof import("@vueuse/core"))["useVModels"] + >; + readonly useVibrate: UnwrapRef< + (typeof import("@vueuse/core"))["useVibrate"] + >; + readonly useVirtualList: UnwrapRef< + (typeof import("@vueuse/core"))["useVirtualList"] + >; + readonly useWakeLock: UnwrapRef< + (typeof import("@vueuse/core"))["useWakeLock"] + >; + readonly useWebNotification: UnwrapRef< + (typeof import("@vueuse/core"))["useWebNotification"] + >; + readonly useWebSocket: UnwrapRef< + (typeof import("@vueuse/core"))["useWebSocket"] + >; + readonly useWebWorker: UnwrapRef< + (typeof import("@vueuse/core"))["useWebWorker"] + >; + readonly useWebWorkerFn: UnwrapRef< + (typeof import("@vueuse/core"))["useWebWorkerFn"] + >; + readonly useWindowFocus: UnwrapRef< + (typeof import("@vueuse/core"))["useWindowFocus"] + >; + readonly useWindowScroll: UnwrapRef< + (typeof import("@vueuse/core"))["useWindowScroll"] + >; + readonly useWindowSize: UnwrapRef< + (typeof import("@vueuse/core"))["useWindowSize"] + >; + readonly watch: UnwrapRef<(typeof import("vue"))["watch"]>; + readonly watchArray: UnwrapRef< + (typeof import("@vueuse/core"))["watchArray"] + >; + readonly watchAtMost: UnwrapRef< + (typeof import("@vueuse/core"))["watchAtMost"] + >; + readonly watchDebounced: UnwrapRef< + (typeof import("@vueuse/core"))["watchDebounced"] + >; + readonly watchDeep: UnwrapRef<(typeof import("@vueuse/core"))["watchDeep"]>; + readonly watchEffect: UnwrapRef<(typeof import("vue"))["watchEffect"]>; + readonly watchIgnorable: UnwrapRef< + (typeof import("@vueuse/core"))["watchIgnorable"] + >; + readonly watchImmediate: UnwrapRef< + (typeof import("@vueuse/core"))["watchImmediate"] + >; + readonly watchOnce: UnwrapRef<(typeof import("@vueuse/core"))["watchOnce"]>; + readonly watchPausable: UnwrapRef< + (typeof import("@vueuse/core"))["watchPausable"] + >; + readonly watchPostEffect: UnwrapRef< + (typeof import("vue"))["watchPostEffect"] + >; + readonly watchSyncEffect: UnwrapRef< + (typeof import("vue"))["watchSyncEffect"] + >; + readonly watchThrottled: UnwrapRef< + (typeof import("@vueuse/core"))["watchThrottled"] + >; + readonly watchTriggerable: UnwrapRef< + (typeof import("@vueuse/core"))["watchTriggerable"] + >; + readonly watchWithFilter: UnwrapRef< + (typeof import("@vueuse/core"))["watchWithFilter"] + >; + readonly whenever: UnwrapRef<(typeof import("@vueuse/core"))["whenever"]>; + } +} diff --git a/src/types/components.d.ts b/src/types/components.d.ts new file mode 100644 index 0000000..452916d --- /dev/null +++ b/src/types/components.d.ts @@ -0,0 +1,72 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module "vue" { + export interface GlobalComponents { + AppLink: (typeof import("./../components/AppLink/index.vue"))["default"]; + AppMain: (typeof import("./../layout/components/AppMain/index.vue"))["default"]; + Breadcrumb: (typeof import("./../components/Breadcrumb/index.vue"))["default"]; + ElAlert: (typeof import("element-plus/es"))["ElAlert"]; + ElBacktop: (typeof import("element-plus/es"))["ElBacktop"]; + ElBadge: (typeof import("element-plus/es"))["ElBadge"]; + ElBreadcrumb: (typeof import("element-plus/es"))["ElBreadcrumb"]; + ElBreadcrumbItem: (typeof import("element-plus/es"))["ElBreadcrumbItem"]; + ElButton: (typeof import("element-plus/es"))["ElButton"]; + ElCard: (typeof import("element-plus/es"))["ElCard"]; + ElCol: (typeof import("element-plus/es"))["ElCol"]; + ElColorPicker: (typeof import("element-plus/es"))["ElColorPicker"]; + ElConfigProvider: (typeof import("element-plus/es"))["ElConfigProvider"]; + ElDialog: (typeof import("element-plus/es"))["ElDialog"]; + ElDivider: (typeof import("element-plus/es"))["ElDivider"]; + ElDrawer: (typeof import("element-plus/es"))["ElDrawer"]; + ElDropdown: (typeof import("element-plus/es"))["ElDropdown"]; + ElDropdownItem: (typeof import("element-plus/es"))["ElDropdownItem"]; + ElDropdownMenu: (typeof import("element-plus/es"))["ElDropdownMenu"]; + ElForm: (typeof import("element-plus/es"))["ElForm"]; + ElFormItem: (typeof import("element-plus/es"))["ElFormItem"]; + ElIcon: (typeof import("element-plus/es"))["ElIcon"]; + ElImage: (typeof import("element-plus/es"))["ElImage"]; + ElInput: (typeof import("element-plus/es"))["ElInput"]; + ElLink: (typeof import("element-plus/es"))["ElLink"]; + ElMenu: (typeof import("element-plus/es"))["ElMenu"]; + ElMenuItem: (typeof import("element-plus/es"))["ElMenuItem"]; + ElRow: (typeof import("element-plus/es"))["ElRow"]; + ElScrollbar: (typeof import("element-plus/es"))["ElScrollbar"]; + ElSubMenu: (typeof import("element-plus/es"))["ElSubMenu"]; + ElSwitch: (typeof import("element-plus/es"))["ElSwitch"]; + ElTabPane: (typeof import("element-plus/es"))["ElTabPane"]; + ElTabs: (typeof import("element-plus/es"))["ElTabs"]; + ElTag: (typeof import("element-plus/es"))["ElTag"]; + ElText: (typeof import("element-plus/es"))["ElText"]; + ElTooltip: (typeof import("element-plus/es"))["ElTooltip"]; + ElWatermark: (typeof import("element-plus/es"))["ElWatermark"]; + Hamburger: (typeof import("./../components/Hamburger/index.vue"))["default"]; + IEpBell: (typeof import("~icons/ep/bell"))["default"]; + IEpClose: (typeof import("~icons/ep/close"))["default"]; + IEpLock: (typeof import("~icons/ep/lock"))["default"]; + IEpUser: (typeof import("~icons/ep/user"))["default"]; + LangSelect: (typeof import("./../components/LangSelect/index.vue"))["default"]; + LayoutSelect: (typeof import("./../layout/components/Settings/components/LayoutSelect.vue"))["default"]; + NavBar: (typeof import("./../layout/components/NavBar/index.vue"))["default"]; + NavbarAction: (typeof import("./../layout/components/NavBar/components/NavbarAction.vue"))["default"]; + NavbarLeft: (typeof import("./../layout/components/NavBar/components/NavbarLeft.vue"))["default"]; + NavbarRight: (typeof import("./../layout/components/NavBar/components/NavbarRight.vue"))["default"]; + RouterLink: (typeof import("vue-router"))["RouterLink"]; + RouterView: (typeof import("vue-router"))["RouterView"]; + Settings: (typeof import("./../layout/components/Settings/index.vue"))["default"]; + Sidebar: (typeof import("./../layout/components/Sidebar/index.vue"))["default"]; + SidebarLogo: (typeof import("./../layout/components/Sidebar/components/SidebarLogo.vue"))["default"]; + SidebarMenu: (typeof import("./../layout/components/Sidebar/components/SidebarMenu.vue"))["default"]; + SidebarMenuItem: (typeof import("./../layout/components/Sidebar/components/SidebarMenuItem.vue"))["default"]; + SidebarMenuItemTitle: (typeof import("./../layout/components/Sidebar/components/SidebarMenuItemTitle.vue"))["default"]; + SidebarMixTopMenu: (typeof import("./../layout/components/Sidebar/components/SidebarMixTopMenu.vue"))["default"]; + SizeSelect: (typeof import("./../components/SizeSelect/index.vue"))["default"]; + SvgIcon: (typeof import("./../components/SvgIcon/index.vue"))["default"]; + TagsView: (typeof import("./../layout/components/TagsView/index.vue"))["default"]; + ThemeColorPicker: (typeof import("./../layout/components/Settings/components/ThemeColorPicker.vue"))["default"]; + } +} diff --git a/src/types/env.d.ts b/src/types/env.d.ts new file mode 100644 index 0000000..7ed1377 --- /dev/null +++ b/src/types/env.d.ts @@ -0,0 +1,40 @@ +// https://cn.vitejs.dev/guide/env-and-mode + +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 类型提示都为 string: https://github.com/vitejs/vite/issues/6930 +interface ImportMetaEnv { + /** 应用端口 */ + VITE_APP_PORT: number; + /** API 基础路径(代理前缀) */ + VITE_APP_BASE_API: string; + /** API 地址 */ + VITE_APP_API_URL: string; + /** 是否开启 Mock 服务 */ + VITE_MOCK_DEV_SERVER: boolean; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + +/** + * 平台的名称、版本、运行所需的`node`版本、依赖、构建时间的类型提示 + */ +declare const __APP_INFO__: { + pkg: { + name: string; + version: string; + engines: { + node: string; + }; + dependencies: Record; + devDependencies: Record; + }; + buildTimestamp: number; +}; diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..146d5bf --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,95 @@ +declare global { + /** + * 响应数据 + */ + interface ResponseData { + code: string; + data: T; + msg: string; + } + + /** + * 分页查询参数 + */ + interface PageQuery { + pageNum: number; + pageSize: number; + } + + /** + * 分页响应对象 + */ + interface PageResult { + /** 数据列表 */ + list: T; + /** 总数 */ + total: number; + } + + /** + * 页签对象 + */ + interface TagView { + /** 页签名称 */ + name: string; + /** 页签标题 */ + title: string; + /** 页签路由路径 */ + path: string; + /** 页签路由完整路径 */ + fullPath: string; + /** 页签图标 */ + icon?: string; + /** 是否固定页签 */ + affix?: boolean; + /** 是否开启缓存 */ + keepAlive?: boolean; + /** 路由查询参数 */ + query?: any; + } + + /** + * 系统设置 + */ + interface AppSettings { + /** 系统标题 */ + title: string; + /** 系统版本 */ + version: string; + /** 是否显示设置 */ + showSettings: boolean; + /** 是否固定头部 */ + fixedHeader: boolean; + /** 是否显示多标签导航 */ + tagsView: boolean; + /** 是否显示侧边栏Logo */ + sidebarLogo: boolean; + /** 导航栏布局(left|top|mix) */ + layout: string; + /** 主题颜色 */ + themeColor: string; + /** 主题模式(dark|light) */ + theme: string; + /** 布局大小(default |large |small) */ + size: string; + /** 语言( zh-cn| en) */ + language: string; + /** 是否开启水印 */ + watermarkEnabled: boolean; + /** 水印内容 */ + watermarkContent: string; + } + + /** + * 组件数据源 + */ + interface OptionType { + /** 值 */ + value: string | number; + /** 文本 */ + label: string; + /** 子列表 */ + children?: OptionType[]; + } +} +export {}; diff --git a/src/types/router.d.ts b/src/types/router.d.ts new file mode 100644 index 0000000..a175ea1 --- /dev/null +++ b/src/types/router.d.ts @@ -0,0 +1,54 @@ +import "vue-router"; + +declare module "vue-router" { + // https://router.vuejs.org/zh/guide/advanced/meta.html#typescript + // 可以通过扩展 RouteMeta 接口来输入 meta 字段 + interface RouteMeta { + /** + * 菜单名称 + * @example 'Dashboard' + */ + title?: string; + + /** + * 菜单图标 + * @example 'el-icon-edit' + */ + icon?: string; + + /** + * 是否隐藏菜单项 + * true 隐藏, false 显示 + * @default false + */ + hidden?: boolean; + + /** + * 始终显示父级菜单,即使只有一个子菜单 + * true 显示父级菜单, false 隐藏父级菜单,显示唯一子节点 + * @default false + */ + alwaysShow?: boolean; + + /** + * 是否固定在页签上 + * true 固定, false 不固定 + * @default false + */ + affix?: boolean; + + /** + * 是否缓存页面 + * true 缓存, false 不缓存 + * @default false + */ + keepAlive?: boolean; + + /** + * 是否在面包屑导航中隐藏 + * true 隐藏, false 显示 + * @default false + */ + breadcrumb?: boolean; + } +} diff --git a/src/utils/baseurls.ts b/src/utils/baseurls.ts new file mode 100644 index 0000000..6f590f6 --- /dev/null +++ b/src/utils/baseurls.ts @@ -0,0 +1,4 @@ +export const BASE_NAME_URL = "/File/User/Name/"; +export const BASE_AVATAR_URL = "/File/User/Portrait/"; +export const BASE_SOFTWARE_URL = "/File/Software/App/"; +export const BASE_FONT_URL = "/File/Fonts/Install/"; diff --git a/src/utils/device-type.ts b/src/utils/device-type.ts new file mode 100644 index 0000000..d50903a --- /dev/null +++ b/src/utils/device-type.ts @@ -0,0 +1,28 @@ +// 判断机型 安卓 or ios +export function isMobile_pc() { + const flag = navigator.userAgent.match( + /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i + ); + return flag; +} +// 判断是在安卓还是ios打开 +export function is_andriod_ios() { + const u = navigator.userAgent; + return { + //移动终端浏览器版本信息 + trident: u.indexOf("Trident") > -1, //IE内核 + presto: u.indexOf("Presto") > -1, //opera内核 + webKit: u.indexOf("AppleWebKit") > -1, //苹果、谷歌内核 + gecko: u.indexOf("Gecko") > -1 && u.indexOf("KHTML") == -1, //火狐内核 + mobile: + !!u.match(/AppleWebKit.*Mobile/i) || + !!u.match( + /MIDP|SymbianOS|NOKIA|SAMSUNG|LG|NEC|TCL|Alcatel|BIRD|DBTEL|Dopod|PHILIPS|HAIER|LENOVO|MOT-|Nokia|SonyEricsson|SIE-|Amoi|ZTE/ + ), //是否为移动终端 + ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端 + android: u.indexOf("Android") > -1 || u.indexOf("Linux") > -1, //android终端或者uc浏览器 + iPhone: u.indexOf("iPhone") > -1 || u.indexOf("Mac") > -1, //是否为iPhone或者QQHD浏览器 + iPad: u.indexOf("iPad") > -1, //是否iPad + webApp: u.indexOf("Safari") == -1, //是否web应该程序,没有头部与底部 + }; +} diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..0ce14b9 --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,120 @@ +/** + * 标准日期显示 + * @param {number} timestamp + * @returns {string} + */ +export function formatDate(timestamp: number | undefined): string { + if (!timestamp) return ""; + + const date = new Date(timestamp * 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + + return `${year}-${month}-${day}`; +} + +/** + * 中文日期显示 + * @param timestamp + * @returns + */ + +export function formatDateCN(timestamp: number | undefined) { + if (!timestamp) return ""; + + const date = new Date(timestamp * 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + + return `${year}年${month}月${day}日`; +} + +/** + * 标准时间显示(到分钟) + * @param {number} timestamp + * @returns {string} + */ +export function formatTimeToMinutes(timestamp: number | undefined): string { + if (!timestamp) return ""; + + const date = new Date(timestamp * 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hour = String(date.getHours()).padStart(2, "0"); + const min = String(date.getMinutes()).padStart(2, "0"); + + return `${year}-${month}-${day} ${hour}:${min}`; +} + +/** + * 标准时间显示(到秒) + * @param {number} timestamp + * @returns {string} + */ +export function formatTimeToSeconds(timestamp: number | undefined): string { + if (!timestamp) return ""; + + const date = new Date(timestamp * 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hour = String(date.getHours()).padStart(2, "0"); + const min = String(date.getMinutes()).padStart(2, "0"); + const sec = String(date.getSeconds()).padStart(2, "0"); + + return `${year}-${month}-${day} ${hour}:${min}:${sec}`; +} + +/** + * 日期转yyyymmdd形式的数字 + * @param date + * @returns + */ +export function dateToNumber(date: Date): number { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const str = `${year}${month}${day}`; + + return Number(str); +} + +const week: { [key: number]: string } = { + 0: "日", + 1: "一", + 2: "二", + 3: "三", + 4: "四", + 5: "五", + 6: "六", +}; +export function dayToWeek(day: number) { + return week[day]; +} + +/** + * 金额格式化 + * @param amount 金额String||Number + * @param precision 小数位数Number(0-20) + * @param unit 是否带单位Boolean + * @returns {string} + */ +export function amountFormat(amount: string | number, precision: number, unit: boolean): string { + if (amount == "") amount = "0"; + precision = precision > 0 && precision <= 20 ? precision : 2; + amount = parseFloat((amount + "").replace(/[^\d\.-]/g, "")).toFixed(precision) + ""; + let l = amount.split(".")[0].split("").reverse(); + let r = amount.split(".")[1]; + let t = ""; + for (let i = 0; i < l.length; i++) { + t += l[i] + ((i + 1) % 3 == 0 && i + 1 != l.length ? "," : ""); + } + if (unit) { + return t.split("").reverse().join("") + "." + r + "元"; + } else { + return t.split("").reverse().join("") + "." + r; + } +} diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts new file mode 100644 index 0000000..17ed904 --- /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 translateRouteTitle(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..fe4ffde --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,51 @@ +/** + * 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; +} + +// 验证邮箱 +export function isEmail(addr: string): boolean { + return /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/.test(addr); +} + +// 验证手机 +export function isPhone(tel: string): boolean { + return /^$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/.test(tel); // //^[1][3,4,5,6,7,8,9][0-9]{9}$/.test(tel); +} diff --git a/src/utils/nprogress.ts b/src/utils/nprogress.ts new file mode 100644 index 0000000..c1d5f23 --- /dev/null +++ b/src/utils/nprogress.ts @@ -0,0 +1,18 @@ +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; + +// 进度条 +NProgress.configure({ + // 动画方式 + easing: "ease", + // 递增进度条的速度 + speed: 500, + // 是否显示加载ico + showSpinner: false, + // 自动递增间隔 + trickleSpeed: 200, + // 初始化时的最小百分比 + minimum: 0.3, +}); + +export default NProgress; diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..bdec5b9 --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,73 @@ +import axios, { InternalAxiosRequestConfig, AxiosResponse } from "axios"; +import { useUserStoreHook } from "@/store/modules/user"; +import { useAppStoreHook } from "@/store/modules/app"; +import { ResultEnum } from "@/enums/ResultEnum"; +import { TOKEN_KEY } from "@/enums/CacheEnum"; +import qs from "qs"; + +// 创建 axios 实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 50000, + headers: { "Content-Type": "application/json;charset=utf-8" }, + paramsSerializer: (params) => { + return qs.stringify(params); + }, +}); + +// 请求拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + const accessToken = localStorage.getItem(TOKEN_KEY); + if (accessToken) { + config.headers.Authorization = accessToken; + } + return config; + }, + (error: any) => { + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + // 检查配置的响应类型是否为二进制类型('blob' 或 'arraybuffer'), 如果是,直接返回响应对象 + if (response.config.responseType === "blob" || response.config.responseType === "arraybuffer") { + return response; + } + + const { code, data, msg } = response.data; + if (code === ResultEnum.SUCCESS) { + return data; + } else if (code === ResultEnum.USER_NAME_EXISTED) { + useAppStoreHook().stopSendCodeCountdown(); + } + ElMessage.error(msg || "系统出错"); + return Promise.reject(new Error(msg || "Error")); + }, + (error: any) => { + // 异常处理 + if (error.response.data) { + const { code, msg } = error.response.data; + if (code === ResultEnum.TOKEN_INVALID) { + ElNotification({ + title: "提示", + message: "您的会话已过期,请重新登录", + type: "info", + }); + useUserStoreHook() + .resetToken() + .then(() => { + location.reload(); + }); + } else { + ElMessage.error(msg || "系统出错"); + } + } + return Promise.reject(error.message); + } +); + +// 导出 axios 实例 +export default service; diff --git a/src/views/account/messages/index.vue b/src/views/account/messages/index.vue new file mode 100644 index 0000000..1851716 --- /dev/null +++ b/src/views/account/messages/index.vue @@ -0,0 +1,240 @@ + + + + diff --git a/src/views/account/notes/index.vue b/src/views/account/notes/index.vue new file mode 100644 index 0000000..98240ae --- /dev/null +++ b/src/views/account/notes/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/account/notes/statement.vue b/src/views/account/notes/statement.vue new file mode 100644 index 0000000..b80e4da --- /dev/null +++ b/src/views/account/notes/statement.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/views/account/notes/trade.vue b/src/views/account/notes/trade.vue new file mode 100644 index 0000000..00cf9c7 --- /dev/null +++ b/src/views/account/notes/trade.vue @@ -0,0 +1,579 @@ + + + + + diff --git a/src/views/account/resetpwd/index.vue b/src/views/account/resetpwd/index.vue new file mode 100644 index 0000000..73ea1ed --- /dev/null +++ b/src/views/account/resetpwd/index.vue @@ -0,0 +1,107 @@ + + + diff --git a/src/views/account/security/index.vue b/src/views/account/security/index.vue new file mode 100644 index 0000000..2cac059 --- /dev/null +++ b/src/views/account/security/index.vue @@ -0,0 +1,506 @@ + + + + + diff --git a/src/views/account/vip/index.vue b/src/views/account/vip/index.vue new file mode 100644 index 0000000..bd32c72 --- /dev/null +++ b/src/views/account/vip/index.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/src/views/autoLogin/index.vue b/src/views/autoLogin/index.vue new file mode 100644 index 0000000..7f20894 --- /dev/null +++ b/src/views/autoLogin/index.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue new file mode 100644 index 0000000..3aa3846 --- /dev/null +++ b/src/views/dashboard/index.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/src/views/demo/internal-doc.vue b/src/views/demo/internal-doc.vue new file mode 100644 index 0000000..df51bac --- /dev/null +++ b/src/views/demo/internal-doc.vue @@ -0,0 +1,28 @@ + + diff --git a/src/views/demo/multi-level/children/children/level3-1.vue b/src/views/demo/multi-level/children/children/level3-1.vue new file mode 100644 index 0000000..888f58e --- /dev/null +++ b/src/views/demo/multi-level/children/children/level3-1.vue @@ -0,0 +1,5 @@ + diff --git a/src/views/demo/multi-level/children/children/level3-2.vue b/src/views/demo/multi-level/children/children/level3-2.vue new file mode 100644 index 0000000..a99c98e --- /dev/null +++ b/src/views/demo/multi-level/children/children/level3-2.vue @@ -0,0 +1,5 @@ + diff --git a/src/views/demo/multi-level/children/level2.vue b/src/views/demo/multi-level/children/level2.vue new file mode 100644 index 0000000..abcc3a7 --- /dev/null +++ b/src/views/demo/multi-level/children/level2.vue @@ -0,0 +1,7 @@ + diff --git a/src/views/demo/multi-level/level1.vue b/src/views/demo/multi-level/level1.vue new file mode 100644 index 0000000..b26146d --- /dev/null +++ b/src/views/demo/multi-level/level1.vue @@ -0,0 +1,15 @@ + diff --git a/src/views/demo/upload.vue b/src/views/demo/upload.vue new file mode 100644 index 0000000..501cb08 --- /dev/null +++ b/src/views/demo/upload.vue @@ -0,0 +1,215 @@ + + + diff --git a/src/views/demo/wang-editor.vue b/src/views/demo/wang-editor.vue new file mode 100644 index 0000000..d7ff4bb --- /dev/null +++ b/src/views/demo/wang-editor.vue @@ -0,0 +1,19 @@ + + + + diff --git a/src/views/error-page/401.vue b/src/views/error-page/401.vue new file mode 100644 index 0000000..afb3e59 --- /dev/null +++ b/src/views/error-page/401.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/views/error-page/404.vue b/src/views/error-page/404.vue new file mode 100644 index 0000000..8223c72 --- /dev/null +++ b/src/views/error-page/404.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/src/views/helper/index.vue b/src/views/helper/index.vue new file mode 100644 index 0000000..7b2adf1 --- /dev/null +++ b/src/views/helper/index.vue @@ -0,0 +1,373 @@ + + + + + diff --git a/src/views/redirect/index.vue b/src/views/redirect/index.vue new file mode 100644 index 0000000..4215bf6 --- /dev/null +++ b/src/views/redirect/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/views/resetPassword/index.vue b/src/views/resetPassword/index.vue new file mode 100644 index 0000000..06bddbe --- /dev/null +++ b/src/views/resetPassword/index.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/src/views/software/index.vue b/src/views/software/index.vue new file mode 100644 index 0000000..9110292 --- /dev/null +++ b/src/views/software/index.vue @@ -0,0 +1,670 @@ + + + + + diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue new file mode 100644 index 0000000..d5daf75 --- /dev/null +++ b/src/views/system/config/index.vue @@ -0,0 +1,327 @@ + + + + diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue new file mode 100644 index 0000000..60d3225 --- /dev/null +++ b/src/views/system/dict/index.vue @@ -0,0 +1,388 @@ + + + + diff --git a/src/views/system/log/index.vue b/src/views/system/log/index.vue new file mode 100644 index 0000000..d234135 --- /dev/null +++ b/src/views/system/log/index.vue @@ -0,0 +1,123 @@ + + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..7c9aedb --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,632 @@ + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..0b792e2 --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,419 @@ + + + diff --git a/src/views/system/user/components/user-import.vue b/src/views/system/user/components/user-import.vue new file mode 100644 index 0000000..a79996f --- /dev/null +++ b/src/views/system/user/components/user-import.vue @@ -0,0 +1,131 @@ + + + diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..094ad94 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,428 @@ + + + + diff --git a/src/views/website/audition/draw.vue b/src/views/website/audition/draw.vue new file mode 100644 index 0000000..00eff94 --- /dev/null +++ b/src/views/website/audition/draw.vue @@ -0,0 +1,209 @@ + + + diff --git a/src/views/website/audition/realname.vue b/src/views/website/audition/realname.vue new file mode 100644 index 0000000..09187a3 --- /dev/null +++ b/src/views/website/audition/realname.vue @@ -0,0 +1,261 @@ + + + diff --git a/src/views/website/notices/index.vue b/src/views/website/notices/index.vue new file mode 100644 index 0000000..c58ef59 --- /dev/null +++ b/src/views/website/notices/index.vue @@ -0,0 +1,367 @@ + + + + diff --git a/src/views/website/question/index.vue b/src/views/website/question/index.vue new file mode 100644 index 0000000..b275044 --- /dev/null +++ b/src/views/website/question/index.vue @@ -0,0 +1,477 @@ + + + diff --git a/src/views/website/statement/notes.vue b/src/views/website/statement/notes.vue new file mode 100644 index 0000000..c2cb231 --- /dev/null +++ b/src/views/website/statement/notes.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/src/views/website/statement/orders.vue b/src/views/website/statement/orders.vue new file mode 100644 index 0000000..7f678e5 --- /dev/null +++ b/src/views/website/statement/orders.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/src/views/website/statistics/index.vue b/src/views/website/statistics/index.vue new file mode 100644 index 0000000..fc4918a --- /dev/null +++ b/src/views/website/statistics/index.vue @@ -0,0 +1,331 @@ + + + + + diff --git a/src/views/website/styles/index.vue b/src/views/website/styles/index.vue new file mode 100644 index 0000000..3627d8b --- /dev/null +++ b/src/views/website/styles/index.vue @@ -0,0 +1,405 @@ + + + + + diff --git a/src/views/website/swVersions/index.vue b/src/views/website/swVersions/index.vue new file mode 100644 index 0000000..f5cdc0a --- /dev/null +++ b/src/views/website/swVersions/index.vue @@ -0,0 +1,619 @@ + + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f08abc3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "node16", + "moduleResolution": "node16", + "strict": true, + "noLib": false, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "allowJs": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "paths": { + "@/*": ["./src/*"] + }, + "types": ["vite/client", "unplugin-icons/types/vue", "element-plus/global"] + }, + "include": ["mock/**/*.ts", "src/**/*.ts", "src/**/*.vue", "vite.config.ts"], + "exclude": ["node_modules", "dist", "**/*.js"] +} diff --git a/uno.config.ts b/uno.config.ts new file mode 100644 index 0000000..b952fe4 --- /dev/null +++ b/uno.config.ts @@ -0,0 +1,43 @@ +// uno.config.ts +import { + defineConfig, + presetAttributify, + presetIcons, + presetTypography, + presetUno, + presetWebFonts, + transformerDirectives, + transformerVariantGroup, +} from "unocss"; + +export default defineConfig({ + shortcuts: { + "flex-center": "flex justify-center items-center", + "flex-x-center": "flex justify-center", + "flex-y-center": "flex items-center", + "wh-full": "w-full h-full", + "flex-x-between": "flex items-center justify-between", + "flex-x-end": "flex items-center justify-end", + "absolute-lt": "absolute left-0 top-0", + "absolute-rt": "absolute right-0 top-0 ", + "fixed-lt": "fixed left-0 top-0", + }, + theme: { + colors: { + primary: "var(--el-color-primary)", + primary_dark: "var(--el-color-primary-light-5)", + }, + }, + presets: [ + presetUno(), + presetAttributify(), + presetIcons(), + presetTypography(), + presetWebFonts({ + fonts: { + // ... + }, + }), + ], + transformers: [transformerDirectives(), transformerVariantGroup()], +}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..69207a8 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,239 @@ +import vue from "@vitejs/plugin-vue"; +import vueJsx from "@vitejs/plugin-vue-jsx"; +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 { resolve } from "path"; +import { name, version, engines, dependencies, devDependencies } from "./package.json"; + +/** @see https://devtools-next.vuejs.org */ +// import VueDevTools from "vite-plugin-vue-devtools"; + +/** 平台的名称、版本、运行所需的`node`版本、依赖、构建时间的类型提示 */ +const __APP_INFO__ = { + pkg: { name, version, engines, dependencies, devDependencies }, + buildTimestamp: Date.now(), +}; + +const pathSrc = resolve(__dirname, "src"); +/** Vite配置 @see https://cn.vitejs.dev/config */ +export default defineConfig(({ mode }: ConfigEnv): UserConfig => { + const env = loadEnv(mode, process.cwd()); + return { + base: "/", + resolve: { + alias: { + "@": pathSrc, + }, + }, + css: { + // CSS 预处理器 + preprocessorOptions: { + // 定义全局 SCSS 变量 + scss: { + // javascriptEnabled: true, + additionalData: ` + @use "@/styles/variables.scss" as *; + `, + }, + }, + }, + server: { + // 允许IP访问 + host: "0.0.0.0", + // 应用端口 (默认:3000) + port: Number(env.VITE_APP_PORT), + // 运行是否自动打开浏览器 + open: true, + proxy: { + /** 代理前缀为 /dev-api 的请求 */ + [env.VITE_APP_BASE_API]: { + changeOrigin: true, + // 接口地址 例如:http://vapi.youlai.tech + target: env.VITE_APP_API_URL, + rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), + }, + }, + }, + plugins: [ + vue(), + // jsx、tsx语法支持 + vueJsx(), + // MOCK 服务 + // env.VITE_MOCK_DEV_SERVER === "true" ? mockDevServerPlugin() : null, + UnoCSS({ + hmrTopLevelAwait: false, + }), + /** 自动导入配置 @see https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts */ + AutoImport({ + // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 + imports: ["vue", "@vueuse/core", "pinia", "vue-router", "vue-i18n"], + resolvers: [ + // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式) + ElementPlusResolver(), + // 自动导入图标组件 + IconsResolver({}), + ], + eslintrc: { + // 是否自动生成 eslint 规则,建议生成之后设置 false + enabled: false, + // 指定自动导入函数 eslint 规则的文件 + filepath: "./.eslintrc-auto-import.json", + globalsPropValue: true, + }, + // 是否在 vue 模板中自动导入 + vueTemplate: true, + // 指定自动导入函数TS类型声明文件路径 (false:关闭自动生成) + dts: false, + // dts: "src/types/auto-imports.d.ts", + }), + Components({ + resolvers: [ + // 自动导入 Element Plus 组件 + ElementPlusResolver(), + // 自动注册图标组件 + IconsResolver({ + // element-plus图标库,其他图标库 https://icon-sets.iconify.design/ + enabledCollections: ["ep"], + }), + ], + // 指定自定义组件位置(默认:src/components) + dirs: ["src/components", "src/**/components"], + // 指定自动导入组件TS类型声明文件路径 (false:关闭自动生成) + dts: false, + // dts: "src/types/components.d.ts", + }), + Icons({ + // 自动安装图标库 + autoInstall: true, + }), + createSvgIconsPlugin({ + // 指定需要缓存的图标文件夹 + iconDirs: [resolve(pathSrc, "assets/icons")], + // 指定symbolId格式 + symbolId: "icon-[dir]-[name]", + }), + ], + // 预加载项目必需的组件 + optimizeDeps: { + include: [ + "vue", + "vue-router", + "pinia", + "axios", + "@vueuse/core", + "path-to-regexp", + "vue-i18n", + "path-browserify", + "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", + "element-plus/es/components/radio-button/style/css", + "element-plus/es/components/checkbox-group/style/css", + "element-plus/es/components/checkbox/style/css", + "element-plus/es/components/tabs/style/css", + "element-plus/es/components/tab-pane/style/css", + "element-plus/es/components/rate/style/css", + "element-plus/es/components/date-picker/style/css", + "element-plus/es/components/notification/style/css", + "element-plus/es/components/image/style/css", + "element-plus/es/components/statistic/style/css", + "element-plus/es/components/watermark/style/css", + "element-plus/es/components/config-provider/style/css", + "element-plus/es/components/text/style/css", + "element-plus/es/components/drawer/style/css", + "element-plus/es/components/color-picker/style/css", + "element-plus/es/components/backtop/style/css", + "element-plus/es/components/message-box/style/css", + "element-plus/es/components/badge/style/css", + ], + }, + // 构建配置 + build: { + chunkSizeWarningLimit: 2000, // 消除打包大小超过500kb警告 + minify: "terser", // Vite 2.6.x 以上需要配置 minify: "terser", terserOptions 才能生效 + terserOptions: { + compress: { + keep_infinity: true, // 防止 Infinity 被压缩成 1/0,这可能会导致 Chrome 上的性能问题 + drop_console: true, // 生产环境去除 console + drop_debugger: true, // 生产环境去除 debugger + }, + format: { + comments: false, // 删除注释 + }, + }, + rollupOptions: { + output: { + manualChunks: { + vue: ["vue", "vue-router", "pinia"], + "vue-i18n": ["vue-i18n"], + "element-plus": ["element-plus"], + }, + // 用于从入口点创建的块的打包输出格式[name]表示文件名,[hash]表示该文件内容hash值 + entryFileNames: "js/[name].[hash].js", + // 用于命名代码拆分时创建的共享块的输出命名 + chunkFileNames: "js/[name].[hash].js", + // 用于输出静态资源的命名,[ext]表示文件扩展名 + assetFileNames: (assetInfo: any) => { + const info = assetInfo.name.split("."); + let extType = info[info.length - 1]; + // console.log('文件信息', assetInfo.name) + if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name)) { + extType = "media"; + } else if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(assetInfo.name)) { + extType = "img"; + } else if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name)) { + extType = "fonts"; + } + return `${extType}/[name].[hash].[ext]`; + }, + }, + }, + }, + define: { + __APP_INFO__: JSON.stringify(__APP_INFO__), + }, + }; +});