commit 66d2c0904bcb024cbd886465fcbf83e37a375ff1 Author: userName <244140623@qq.com> Date: Fri Mar 15 11:07:18 2024 +0800 代码初始化 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5b1a804 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# 告诉EditorConfig插件,这是根文件,不用继续往上查找 +root = true + +# 匹配全部文件 +[*] +# 缩进风格,可选space、tab +indent_style = space +# 缩进的空格数 +indent_size = 2 +# 设置字符集 +charset = utf-8 +# 结尾换行符,可选lf、cr、crlf +end_of_line = lf +# 在文件结尾插入新行 +trim_trailing_whitespace = true +# 删除一行中的前后空格 +insert_final_newline = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..7edd98a --- /dev/null +++ b/.env.development @@ -0,0 +1,30 @@ +# 页面标题 +VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统 + +# 开发环境配置 +VITE_APP_ENV = 'development' + +# 开发环境 +VITE_APP_BASE_API = '/dev-api' + +# 应用访问路径 例如使用前缀 /admin/ +VITE_APP_CONTEXT_PATH = '/' + +# 监控地址 +VITE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications' + +# powerjob 控制台地址 +VITE_APP_POWERJOB_ADMIN = 'http://localhost:7700/' + +VITE_APP_PORT = 80 + +# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换 +VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' +# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换 +VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=' + +# 客户端id +VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e' + +# websocket 开关(开发环境默认关闭ws 因vite的bug导致如ws无法连接则会崩溃) +VITE_APP_WEBSOCKET = false diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..0e3b9fc --- /dev/null +++ b/.env.production @@ -0,0 +1,33 @@ +# 页面标题 +VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统 + +# 生产环境配置 +VITE_APP_ENV = 'production' + +# 应用访问路径 例如使用前缀 /admin/ +VITE_APP_CONTEXT_PATH = '/' + +# 监控地址 +VITE_APP_MONITRO_ADMIN = '/admin/applications' + +# powerjob 控制台地址 +VITE_APP_POWERJOB_ADMIN = '/powerjob' + +# 生产环境 +VITE_APP_BASE_API = '/prod-api' + +# 是否在打包时开启压缩,支持 gzip 和 brotli +VITE_BUILD_COMPRESS = gzip + +VITE_APP_PORT = 80 + +# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换 +VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' +# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换 +VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=' + +# 客户端id +VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e' + +# websocket 开关 +VITE_APP_WEBSOCKET = true diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..c4d771a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,17 @@ +*.sh +node_modules +*.md +*.woff +*.ttf +.vscode +.idea +dist +/public +/docs +.husky +.local +/bin +.eslintrc.js +prettier.config.js +src/assets +tailwind.config.js \ No newline at end of file diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json new file mode 100644 index 0000000..a6661b6 --- /dev/null +++ b/.eslintrc-auto-import.json @@ -0,0 +1,312 @@ +{ + "globals": { + "ComponentInternalInstance": true, + "TransferKey": true, + "ElFormRules": true, + "CheckboxValueType": true, + "PropType": true, + "DateModelType": true, + "UploadFile": true, + "ElFormInstance": true, + "ElTableInstance": true, + "ElTreeInstance": true, + "ElTreeSelectInstance": true, + "ElSelectInstance": true, + "ElUploadInstance": true, + "ElCardInstance": true, + "ElDialogInstance": true, + "ElInputInstance": true, + "ElInputNumberInstance": true, + "ElRadioInstance": true, + "ElRadioGroupInstance": true, + "ElRadioButtonInstance": true, + "ElCheckboxInstance": true, + "ElCheckboxGroupInstance": true, + "ElSwitchInstance": true, + "ElDatePickerInstance": true, + "ElTimePickerInstance": true, + "ElTimeSelectInstance": true, + "ElScrollbarInstance": true, + "ElCascaderInstance": true, + "ElColorPickerInstance": true, + "ElRateInstance": true, + "ElSliderInstance": true, + "useRouter": true, + "useRoute": true, + "EffectScope": true, + "ElTable": true, + "ElSelect": true, + "ElUpload": true, + "ElForm": true, + "ElTree": true, + "ElMessage": true, + "ElMessageBox": true, + "asyncComputed": true, + "autoResetRef": true, + "computed": true, + "computedAsync": true, + "computedEager": true, + "computedInject": true, + "computedWithControl": true, + "controlledComputed": true, + "controlledRef": true, + "createApp": true, + "createEventHook": true, + "createGlobalState": true, + "createInjectionState": true, + "createReactiveFn": true, + "createSharedComposable": true, + "createUnrefFn": true, + "customRef": true, + "debouncedRef": true, + "debouncedWatch": true, + "defineAsyncComponent": true, + "defineComponent": true, + "eagerComputed": true, + "effectScope": true, + "extendRef": true, + "getCurrentInstance": true, + "getCurrentScope": true, + "h": true, + "ignorableWatch": true, + "inject": true, + "isDefined": true, + "isProxy": true, + "isReactive": true, + "isReadonly": true, + "isRef": true, + "makeDestructurable": true, + "markRaw": true, + "nextTick": true, + "onActivated": true, + "onBeforeMount": true, + "onBeforeUnmount": true, + "onBeforeUpdate": true, + "onClickOutside": true, + "onDeactivated": true, + "onErrorCaptured": true, + "onKeyStroke": true, + "onLongPress": true, + "onMounted": true, + "onRenderTracked": true, + "onRenderTriggered": true, + "onScopeDispose": true, + "onServerPrefetch": true, + "onStartTyping": true, + "onUnmounted": true, + "onUpdated": true, + "pausableWatch": true, + "provide": true, + "reactify": true, + "reactifyObject": true, + "reactive": true, + "reactiveComputed": true, + "reactiveOmit": true, + "reactivePick": true, + "readonly": true, + "ref": true, + "refAutoReset": true, + "refDebounced": true, + "refDefault": true, + "refThrottled": true, + "refWithControl": true, + "resolveComponent": true, + "resolveDirective": true, + "resolveRef": true, + "resolveUnref": true, + "shallowReactive": true, + "shallowReadonly": true, + "shallowRef": true, + "syncRef": true, + "syncRefs": true, + "templateRef": true, + "throttledRef": true, + "throttledWatch": true, + "toRaw": true, + "toReactive": true, + "toRef": true, + "toRefs": true, + "triggerRef": true, + "tryOnBeforeMount": true, + "tryOnBeforeUnmount": true, + "tryOnMounted": true, + "tryOnScopeDispose": true, + "tryOnUnmounted": true, + "unref": true, + "unrefElement": true, + "until": true, + "useActiveElement": true, + "useArrayEvery": true, + "useArrayFilter": true, + "useArrayFind": true, + "useArrayFindIndex": true, + "useArrayFindLast": true, + "useArrayJoin": true, + "useArrayMap": true, + "useArrayReduce": true, + "useArraySome": true, + "useArrayUnique": true, + "useAsyncQueue": true, + "useAsyncState": true, + "useAttrs": true, + "useBase64": true, + "useBattery": true, + "useBluetooth": true, + "useBreakpoints": true, + "useBroadcastChannel": true, + "useBrowserLocation": true, + "useCached": true, + "useClipboard": true, + "useCloned": true, + "useColorMode": true, + "useConfirmDialog": true, + "useCounter": true, + "useCssModule": true, + "useCssVar": true, + "useCssVars": true, + "useCurrentElement": true, + "useCycleList": true, + "useDark": true, + "useDateFormat": true, + "useDebounce": true, + "useDebounceFn": true, + "useDebouncedRefHistory": true, + "useDeviceMotion": true, + "useDeviceOrientation": true, + "useDevicePixelRatio": true, + "useDevicesList": true, + "useDisplayMedia": true, + "useDocumentVisibility": true, + "useDraggable": true, + "useDropZone": true, + "useElementBounding": true, + "useElementByPoint": true, + "useElementHover": true, + "useElementSize": true, + "useElementVisibility": true, + "useEventBus": true, + "useEventListener": true, + "useEventSource": true, + "useEyeDropper": true, + "useFavicon": true, + "useFetch": true, + "useFileDialog": true, + "useFileSystemAccess": true, + "useFocus": true, + "useFocusWithin": true, + "useFps": true, + "useFullscreen": true, + "useGamepad": true, + "useGeolocation": true, + "useIdle": true, + "useImage": true, + "useInfiniteScroll": true, + "useIntersectionObserver": true, + "useInterval": true, + "useIntervalFn": true, + "useKeyModifier": true, + "useLastChanged": true, + "useLocalStorage": true, + "useMagicKeys": true, + "useManualRefHistory": true, + "useMediaControls": true, + "useMediaQuery": true, + "useMemoize": true, + "useMemory": true, + "useMounted": true, + "useMouse": true, + "useMouseInElement": true, + "useMousePressed": true, + "useMutationObserver": true, + "useNavigatorLanguage": true, + "useNetwork": true, + "useNow": true, + "useObjectUrl": true, + "useOffsetPagination": true, + "useOnline": true, + "usePageLeave": true, + "useParallax": true, + "usePermission": true, + "usePointer": true, + "usePointerLock": true, + "usePointerSwipe": true, + "usePreferredColorScheme": true, + "usePreferredContrast": true, + "usePreferredDark": true, + "usePreferredLanguages": true, + "usePreferredReducedMotion": true, + "usePrevious": true, + "useRafFn": true, + "useRefHistory": true, + "useResizeObserver": true, + "useScreenOrientation": true, + "useScreenSafeArea": true, + "useScriptTag": true, + "useScroll": true, + "useScrollLock": true, + "useSessionStorage": true, + "useShare": true, + "useSlots": true, + "useSorted": true, + "useSpeechRecognition": true, + "useSpeechSynthesis": true, + "useStepper": true, + "useStorage": true, + "useStorageAsync": true, + "useStyleTag": true, + "useSupported": true, + "useSwipe": true, + "useTemplateRefsList": true, + "useTextDirection": true, + "useTextSelection": true, + "useTextareaAutosize": true, + "useThrottle": true, + "useThrottleFn": true, + "useThrottledRefHistory": true, + "useTimeAgo": true, + "useTimeout": true, + "useTimeoutFn": true, + "useTimeoutPoll": true, + "useTimestamp": true, + "useTitle": true, + "useToNumber": true, + "useToString": true, + "useToggle": true, + "useTransition": true, + "useUrlSearchParams": true, + "useUserMedia": true, + "useVModel": true, + "useVModels": true, + "useVibrate": true, + "useVirtualList": true, + "useWakeLock": true, + "useWebNotification": true, + "useWebSocket": true, + "useWebWorker": true, + "useWebWorkerFn": true, + "useWindowFocus": true, + "useWindowScroll": true, + "useWindowSize": true, + "watch": true, + "watchArray": true, + "watchAtMost": true, + "watchDebounced": true, + "watchEffect": true, + "watchIgnorable": true, + "watchOnce": true, + "watchPausable": true, + "watchPostEffect": true, + "watchSyncEffect": true, + "watchThrottled": true, + "watchTriggerable": true, + "watchWithFilter": true, + "whenever": true, + "ImportOption": true, + "TreeType": true, + "FieldOption": true, + "PageData": true, + "storeToRefs": true, + "DictDataOption": true, + "UploadOption": true + } +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a1b32e3 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,42 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true + }, + parser: 'vue-eslint-parser', + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-essential', + 'plugin:@typescript-eslint/recommended', + './.eslintrc-auto-import.json', + 'plugin:prettier/recommended' + ], + parserOptions: { + ecmaVersion: '2020', + sourceType: 'module', + parser: '@typescript-eslint/parser' + }, + plugins: ['vue', '@typescript-eslint'], + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'vue/no-v-model-argument': 'off', + '@typescript-eslint/ban-types': [ + 'error', + { + // 关闭空类型检查 {} + extendDefaults: true, + types: { + '{}': false, + Function: false + } + } + ] + }, + globals: { + DialogOption: 'readonly', + OptionType: 'readonly' + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40df474 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +.DS_Store +.history +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +**/*.log + +tests/**/coverage/ +tests/e2e/reports +selenium-debug.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock + +# 编译生成的文件 +auto-imports.d.ts +components.d.ts diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d251d2e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +/dist/* +.local +.output.js +/node_modules/** + +**/*.svg +**/*.sh + +/public/* \ No newline at end of file diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..61d2d92 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,46 @@ +/** + * 代码格式化配置 + */ +module.exports = { + // 一行最多多少个字符 + printWidth: 150, + // 指定每个缩进级别的空格数 + tabWidth: 2, + // 使用制表符而不是空格缩进行 + useTabs: false, + // 在语句末尾是否需要分号 + semi: true, + // 是否使用单引号 + singleQuote: true, + // 更改引用对象属性的时间 可选值"" + quoteProps: 'as-needed', + // 在JSX中使用单引号而不是双引号 + jsxSingleQuote: false, + // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"",默认none + trailingComma: 'none', + // 在对象文字中的括号之间打印空格 + bracketSpacing: true, + // jsx 标签的反尖括号需要换行 + jsxBracketSameLine: false, + embeddedLanguageFormatting: 'off', + // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x + arrowParens: 'always', + // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码 + rangeStart: 0, + rangeEnd: Infinity, + // 指定要使用的解析器,不需要写文件开头的 @prettier + requirePragma: false, + // 不需要自动在文件开头插入 @prettier + insertPragma: false, + // 使用默认的折行标准 always\never\preserve + proseWrap: 'preserve', + // 指定HTML文件的全局空格敏感度 css\strict\ignore + htmlWhitespaceSensitivity: 'css', + // Vue文件脚本和样式标签缩进 + vueIndentScriptAndStyle: false, + // 在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF), + // 然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。 + // 对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾 + // 换行符使用 lf 结尾是 可选值"" + endOfLine: 'auto' +}; diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..32b3071 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2019 RuoYi-Vue-Plus + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2742463 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +## 平台简介 + +* 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。 +* 配套后端代码仓库地址 +* [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus) +* [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus) + +## 前端运行 + +```bash +# 克隆项目 +git clone https://gitee.com/JavaLionLi/plus-ui.git + +# 安装依赖 +npm install --registry=https://registry.npmmirror.com + +# 启动服务 +npm run dev + +# 构建生产环境 +npm run build:prod + +# 前端访问地址 http://localhost:80 +``` + +## 本框架与RuoYi的业务差异 + +| 业务 | 功能说明 | 本框架 | RuoYi | +|--------|-----------------------------------------|-----|------------------| +| 租户管理 | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等 | 支持 | 无 | +| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等 | 支持 | 无 | +| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 | +| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 | +| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 | +| 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等 | 支持 | 支持 | +| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | 支持 | 支持 | +| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | 支持 | 支持 | +| 参数管理 | 对系统动态配置常用参数 | 支持 | 支持 | +| 通知公告 | 系统通知公告信息发布维护 | 支持 | 支持 | +| 操作日志 | 系统正常操作日志记录和查询 系统异常信息日志记录和查询 | 支持 | 支持 | +| 登录日志 | 系统登录日志记录查询包含登录异常 | 支持 | 支持 | +| 文件管理 | 系统文件展示、上传、下载、删除等管理 | 支持 | 无 | +| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理 | 支持 | 无 | +| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作 | 支持 | 支持 | +| 定时任务 | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等 | 支持 | 仅支持任务与日志管理 | +| 代码生成 | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持 | 仅支持单数据源 | +| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 | +| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 | +| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 | +| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 | +| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 | + +## 演示图例 + +| | | +|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png "屏幕截图") | +| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png "屏幕截图") | diff --git a/bin/build.bat b/bin/build.bat new file mode 100644 index 0000000..ecbb454 --- /dev/null +++ b/bin/build.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅdistļ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn build:prod + +pause \ No newline at end of file diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..f5b24e0 --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] װWeḅnode_modulesļ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn --registry=https://registry.npmmirror.com + +pause \ No newline at end of file diff --git a/bin/run-web.bat b/bin/run-web.bat new file mode 100644 index 0000000..d2fe397 --- /dev/null +++ b/bin/run-web.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] ʹ Vite Web ̡ +echo. + +%~d0 +cd %~dp0 + +cd .. +yarn dev + +pause \ No newline at end of file diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..3b75ed5 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,22 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', // 新功能 feature + 'fix', // 修复 bug + 'docs', // 文档注释 + 'style', // 代码格式 + 'refactor', // 重构 + 'perf', // 性能优化 + 'test', // 增加测试 + 'chore', // 构建过程或辅助工具的变动 + 'revert', // 回退 + 'build' // 打包 + ] + ], + 'subject-case': [0] + } +}; diff --git a/html/ie.html b/html/ie.html new file mode 100644 index 0000000..87d262d --- /dev/null +++ b/html/ie.html @@ -0,0 +1,60 @@ + + + + + 请升级您的浏览器 + + + + + + +

请升级您的浏览器,以便我们更好的为您提供服务!

+

您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。

+
+

请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束

+

+ 自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 + 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 + 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明 + 。 +

+
+

您可以选择更先进的浏览器

+

推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。

+ +
+ + diff --git a/index.html b/index.html new file mode 100644 index 0000000..c0dab51 --- /dev/null +++ b/index.html @@ -0,0 +1,217 @@ + + + + + + + + + RuoYi-Vue-Plus多租户管理系统 + + + + + +
+
+
+
+
+
正在加载系统资源,请耐心等待
+
+
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..6af6ba2 --- /dev/null +++ b/package.json @@ -0,0 +1,84 @@ +{ + "name": "ruoyi-vue-plus", + "version": "5.1.2", + "description": "RuoYi-Vue-Plus多租户管理系统", + "author": "LionLi", + "license": "MIT", + "scripts": { + "dev": "vite serve --mode development", + "build:prod": "vite build --mode production &&vue-tsc --noEmit", + "preview": "vite preview", + "lint": "eslint src/**/*.{ts,js,vue} --fix", + "prepare": "husky install", + "prettier": "prettier --write ." + }, + "repository": { + "type": "git", + "url": "https://gitee.com/JavaLionLi/plus-ui.git" + }, + "dependencies": { + "@element-plus/icons-vue": "2.1.0", + "@vueup/vue-quill": "1.2.0", + "@vueuse/core": "9.5.0", + "animate.css": "4.1.1", + "await-to-js": "^3.0.0", + "axios": "^1.3.4", + "echarts": "5.4.0", + "element-plus": "2.2.27", + "file-saver": "2.0.5", + "fuse.js": "6.6.2", + "js-cookie": "3.0.1", + "jsencrypt": "3.3.1", + "crypto-js": "^4.1.1", + "nprogress": "0.2.0", + "path-browserify": "1.0.1", + "path-to-regexp": "6.2.0", + "pinia": "2.0.22", + "screenfull": "6.0.0", + "vform3-builds": "3.0.8", + "vue": "3.2.45", + "vue-cropper": "1.0.3", + "vue-i18n": "9.2.2", + "vue-router": "4.1.4", + "vue-types": "^5.0.3" + }, + "devDependencies": { + "@iconify/json": "^2.2.40", + "@intlify/unplugin-vue-i18n": "0.8.2", + "@types/crypto-js": "^4.1.1", + "@types/file-saver": "2.0.5", + "@types/js-cookie": "3.0.3", + "@types/node": "18.14.2", + "@types/nprogress": "0.2.0", + "@types/path-browserify": "^1.0.0", + "@typescript-eslint/eslint-plugin": "5.56.0", + "@typescript-eslint/parser": "5.56.0", + "@unocss/preset-attributify": "^0.50.6", + "@unocss/preset-icons": "^0.50.6", + "@unocss/preset-uno": "^0.50.6", + "@vitejs/plugin-vue": "4.0.0", + "@vue/compiler-sfc": "3.2.45", + "autoprefixer": "10.4.14", + "eslint": "8.36.0", + "eslint-config-prettier": "8.8.0", + "eslint-plugin-prettier": "4.2.1", + "eslint-plugin-vue": "9.9.0", + "fast-glob": "^3.2.11", + "husky": "7.0.4", + "postcss": "^8.4.21", + "prettier": "2.8.6", + "sass": "1.56.1", + "typescript": "4.9.5", + "unocss": "^0.50.6", + "unplugin-auto-import": "0.13.0", + "unplugin-icons": "0.15.1", + "unplugin-vue-components": "0.23.0", + "vite": "4.3.1", + "vite-plugin-compression": "0.5.1", + "vite-plugin-svg-icons": "2.0.1", + "unplugin-vue-setup-extend-plus": "0.4.9", + "vitest": "^0.29.7", + "vue-eslint-parser": "9.1.0", + "vue-tsc": "0.35.0" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..3f919d8 Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..41ffdd4 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/animate.ts b/src/animate.ts new file mode 100644 index 0000000..7a23df8 --- /dev/null +++ b/src/animate.ts @@ -0,0 +1,48 @@ +// 前缀 +const animatePrefix = 'animate__animated '; +// 开启随机动画 随机动画值 +const animateList: string[] = [ + animatePrefix + 'animate__pulse', + animatePrefix + 'animate__rubberBand', + animatePrefix + 'animate__bounceIn', + animatePrefix + 'animate__bounceInLeft', + animatePrefix + 'animate__fadeIn', + animatePrefix + 'animate__fadeInLeft', + animatePrefix + 'animate__fadeInDown', + animatePrefix + 'animate__fadeInUp', + animatePrefix + 'animate__flipInX', + animatePrefix + 'animate__lightSpeedInLeft', + animatePrefix + 'animate__rotateInDownLeft', + animatePrefix + 'animate__rollIn', + animatePrefix + 'animate__rotateInDownLeft', + animatePrefix + 'animate__zoomIn', + animatePrefix + 'animate__zoomInDown', + animatePrefix + 'animate__slideInLeft', + animatePrefix + 'animate__lightSpeedIn' +]; +// 关闭随机动画后的默认效果 +const defaultAnimate = animatePrefix + 'animate__fadeIn'; +// 搜索隐藏显示动画 +const searchAnimate = { + enter: '', + leave: '' +}; + +// 菜单搜索动画 +const menuSearchAnimate = { + enter: animatePrefix + 'animate__fadeIn', + leave: animatePrefix + 'animate__fadeOut' +}; +// logo动画 +const logoAnimate = { + enter: animatePrefix + 'animate__fadeIn', + leave: animatePrefix + 'animate__fadeOut' +}; + +export default { + animateList, + defaultAnimate, + searchAnimate, + menuSearchAnimate, + logoAnimate +}; diff --git a/src/api/demo/demo/index.ts b/src/api/demo/demo/index.ts new file mode 100644 index 0000000..7441720 --- /dev/null +++ b/src/api/demo/demo/index.ts @@ -0,0 +1,62 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { DemoVO, DemoForm, DemoQuery } from '@/api/demo/demo/types'; + +/** + * 查询测试单列表 + * @param query + * @returns {*} + */ +export const listDemo = (query?: DemoQuery): AxiosPromise => { + return request({ + url: '/demo/demo/list', + method: 'get', + params: query + }); +}; + +/** + * 查询测试单详细 + * @param id + */ +export const getDemo = (id: string | number): AxiosPromise => { + return request({ + url: '/demo/demo/' + id, + method: 'get' + }); +}; + +/** + * 新增测试单 + * @param data + */ +export const addDemo = (data: DemoForm) => { + return request({ + url: '/demo/demo', + method: 'post', + data: data + }); +}; + +/** + * 修改测试单 + * @param data + */ +export const updateDemo = (data: DemoForm) => { + return request({ + url: '/demo/demo', + method: 'put', + data: data + }); +}; + +/** + * 删除测试单 + * @param id + */ +export const delDemo = (id: string | number | Array) => { + return request({ + url: '/demo/demo/' + id, + method: 'delete' + }); +}; diff --git a/src/api/demo/demo/types.ts b/src/api/demo/demo/types.ts new file mode 100644 index 0000000..ea51d32 --- /dev/null +++ b/src/api/demo/demo/types.ts @@ -0,0 +1,90 @@ +export interface DemoVO { + /** + * 主键 + */ + id: string | number; + + /** + * 部门id + */ + deptId: string | number; + + /** + * 用户id + */ + userId: string | number; + + /** + * 排序号 + */ + orderNum: number; + + /** + * key键 + */ + testKey: string; + + /** + * 值 + */ + value: string; +} + +export interface DemoForm extends BaseEntity { + /** + * 主键 + */ + id?: string | number; + + /** + * 部门id + */ + deptId?: string | number; + + /** + * 用户id + */ + userId?: string | number; + + /** + * 排序号 + */ + orderNum?: number; + + /** + * key键 + */ + testKey?: string; + + /** + * 值 + */ + value?: string; +} + +export interface DemoQuery extends PageQuery { + /** + * 部门id + */ + deptId?: string | number; + + /** + * 用户id + */ + userId?: string | number; + + /** + * 排序号 + */ + orderNum?: number; + + /** + * key键 + */ + testKey?: string; + + /** + * 值 + */ + value?: string; +} diff --git a/src/api/demo/tree/index.ts b/src/api/demo/tree/index.ts new file mode 100644 index 0000000..562deb6 --- /dev/null +++ b/src/api/demo/tree/index.ts @@ -0,0 +1,62 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { TreeVO, TreeForm, TreeQuery } from '@/api/demo/tree/types'; + +/** + * 查询测试树列表 + * @param query + * @returns {*} + */ +export const listTree = (query?: TreeQuery): AxiosPromise => { + return request({ + url: '/demo/tree/list', + method: 'get', + params: query + }); +}; + +/** + * 查询测试树详细 + * @param id + */ +export const getTree = (id: string | number): AxiosPromise => { + return request({ + url: '/demo/tree/' + id, + method: 'get' + }); +}; + +/** + * 新增测试树 + * @param data + */ +export const addTree = (data: TreeForm) => { + return request({ + url: '/demo/tree', + method: 'post', + data: data + }); +}; + +/** + * 修改测试树 + * @param data + */ +export const updateTree = (data: TreeForm) => { + return request({ + url: '/demo/tree', + method: 'put', + data: data + }); +}; + +/** + * 删除测试树 + * @param id + */ +export const delTree = (id: string | number | Array) => { + return request({ + url: '/demo/tree/' + id, + method: 'delete' + }); +}; diff --git a/src/api/demo/tree/types.ts b/src/api/demo/tree/types.ts new file mode 100644 index 0000000..e164d8b --- /dev/null +++ b/src/api/demo/tree/types.ts @@ -0,0 +1,80 @@ +export interface TreeVO { + /** + * 主键 + */ + id: string | number; + + /** + * 父id + */ + parentId: string | number; + + /** + * 部门id + */ + deptId: string | number; + + /** + * 用户id + */ + userId: string | number; + + /** + * 值 + */ + treeName: string; + + /** + * 子对象 + */ + children: TreeVO[]; +} + +export interface TreeForm extends BaseEntity { + /** + * 主键 + */ + id?: string | number; + + /** + * 父id + */ + parentId?: string | number; + + /** + * 部门id + */ + deptId?: string | number; + + /** + * 用户id + */ + userId?: string | number; + + /** + * 值 + */ + treeName?: string; +} + +export interface TreeQuery { + /** + * 父id + */ + parentId?: string | number; + + /** + * 部门id + */ + deptId?: string | number; + + /** + * 用户id + */ + userId?: string | number; + + /** + * 值 + */ + treeName?: string; +} diff --git a/src/api/login.ts b/src/api/login.ts new file mode 100644 index 0000000..100a5e9 --- /dev/null +++ b/src/api/login.ts @@ -0,0 +1,105 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { LoginData, LoginResult, VerifyCodeResult, TenantInfo } from './types'; +import { UserInfo } from '@/api/system/user/types'; + +// pc端固定客户端授权id +const clientId = import.meta.env.VITE_APP_CLIENT_ID; + +/** + * @param data {LoginData} + * @returns + */ +export function login(data: LoginData): AxiosPromise { + const params = { + ...data, + clientId: data.clientId || clientId, + grantType: data.grantType || 'password' + }; + return request({ + url: '/auth/login', + headers: { + isToken: false, + isEncrypt: true + }, + method: 'post', + data: params + }); +} + +// 注册方法 +export function register(data: any) { + const params = { + ...data, + clientId: clientId, + grantType: 'password' + }; + return request({ + url: '/auth/register', + headers: { + isToken: false, + isEncrypt: true + }, + method: 'post', + data: params + }); +} + +/** + * 注销 + */ +export function logout() { + return request({ + url: '/auth/logout', + method: 'post' + }); +} + +/** + * 获取验证码 + */ +export function getCodeImg(): AxiosPromise { + return request({ + url: '/auth/code', + headers: { + isToken: false + }, + method: 'get', + timeout: 20000 + }); +} + +/** + * 第三方登录 + */ +export function callback(data: LoginData): AxiosPromise { + const LoginData = { + ...data, + clientId: clientId, + grantType: 'social' + }; + return request({ + url: '/auth/social/callback', + method: 'post', + data: LoginData + }); +} + +// 获取用户详细信息 +export function getInfo(): AxiosPromise { + return request({ + url: '/system/user/getInfo', + method: 'get' + }); +} + +// 获取租户列表 +export function getTenantList(): AxiosPromise { + return request({ + url: '/auth/tenant/list', + headers: { + isToken: false + }, + method: 'get' + }); +} diff --git a/src/api/menu.ts b/src/api/menu.ts new file mode 100644 index 0000000..a3ae80e --- /dev/null +++ b/src/api/menu.ts @@ -0,0 +1,11 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { RouteRecordRaw } from 'vue-router'; + +// 获取路由 +export function getRouters(): AxiosPromise { + return request({ + url: '/system/menu/getRouters', + method: 'get' + }); +} diff --git a/src/api/monitor/cache/index.ts b/src/api/monitor/cache/index.ts new file mode 100644 index 0000000..e45d6fb --- /dev/null +++ b/src/api/monitor/cache/index.ts @@ -0,0 +1,59 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { CacheVO } from './types'; + +// 查询缓存详细 +export function getCache(): AxiosPromise { + return request({ + url: '/monitor/cache', + method: 'get' + }); +} + +// 查询缓存名称列表 +export function listCacheName() { + return request({ + url: '/monitor/cache/getNames', + method: 'get' + }); +} + +// 查询缓存键名列表 +export function listCacheKey(cacheName: string) { + return request({ + url: '/monitor/cache/getKeys/' + cacheName, + method: 'get' + }); +} + +// 查询缓存内容 +export function getCacheValue(cacheName: string, cacheKey: string) { + return request({ + url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, + method: 'get' + }); +} + +// 清理指定名称缓存 +export function clearCacheName(cacheName: string) { + return request({ + url: '/monitor/cache/clearCacheName/' + cacheName, + method: 'delete' + }); +} + +// 清理指定键名缓存 +export function clearCacheKey(cacheName: string, cacheKey: string) { + return request({ + url: '/monitor/cache/clearCacheKey/' + cacheName + '/' + cacheKey, + method: 'delete' + }); +} + +// 清理全部缓存 +export function clearCacheAll() { + return request({ + url: '/monitor/cache/clearCacheAll', + method: 'delete' + }); +} diff --git a/src/api/monitor/cache/types.ts b/src/api/monitor/cache/types.ts new file mode 100644 index 0000000..4017b65 --- /dev/null +++ b/src/api/monitor/cache/types.ts @@ -0,0 +1,7 @@ +export interface CacheVO { + commandStats: Array<{ name: string; value: string }>; + + dbSize: number; + + info: { [key: string]: string }; +} diff --git a/src/api/monitor/loginInfo/index.ts b/src/api/monitor/loginInfo/index.ts new file mode 100644 index 0000000..f8877c9 --- /dev/null +++ b/src/api/monitor/loginInfo/index.ts @@ -0,0 +1,36 @@ +import request from '@/utils/request'; +import { LoginInfoQuery, LoginInfoVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询登录日志列表 +export function list(query: LoginInfoQuery): AxiosPromise { + return request({ + url: '/monitor/logininfor/list', + method: 'get', + params: query + }); +} + +// 删除登录日志 +export function delLoginInfo(infoId: string | number | Array) { + return request({ + url: '/monitor/logininfor/' + infoId, + method: 'delete' + }); +} + +// 解锁用户登录状态 +export function unlockLoginInfo(userName: string | Array) { + return request({ + url: '/monitor/logininfor/unlock/' + userName, + method: 'get' + }); +} + +// 清空登录日志 +export function cleanLoginInfo() { + return request({ + url: '/monitor/logininfor/clean', + method: 'delete' + }); +} diff --git a/src/api/monitor/loginInfo/types.ts b/src/api/monitor/loginInfo/types.ts new file mode 100644 index 0000000..202c779 --- /dev/null +++ b/src/api/monitor/loginInfo/types.ts @@ -0,0 +1,20 @@ +export interface LoginInfoVO { + infoId: string | number; + tenantId: string | number; + userName: string; + status: string; + ipaddr: string; + loginLocation: string; + browser: string; + os: string; + msg: string; + loginTime: string; +} + +export interface LoginInfoQuery extends PageQuery { + ipaddr: string; + userName: string; + status: string; + orderByColumn: string; + isAsc: string; +} diff --git a/src/api/monitor/online/index.ts b/src/api/monitor/online/index.ts new file mode 100644 index 0000000..3d9034a --- /dev/null +++ b/src/api/monitor/online/index.ts @@ -0,0 +1,20 @@ +import request from '@/utils/request'; +import { OnlineQuery, OnlineVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询在线用户列表 +export function list(query: OnlineQuery): AxiosPromise { + return request({ + url: '/monitor/online/list', + method: 'get', + params: query + }); +} + +// 强退用户 +export function forceLogout(tokenId: string) { + return request({ + url: '/monitor/online/' + tokenId, + method: 'delete' + }); +} diff --git a/src/api/monitor/online/types.ts b/src/api/monitor/online/types.ts new file mode 100644 index 0000000..8c0ec27 --- /dev/null +++ b/src/api/monitor/online/types.ts @@ -0,0 +1,15 @@ +export interface OnlineQuery extends PageQuery { + ipaddr: string; + userName: string; +} + +export interface OnlineVO extends BaseEntity { + tokenId: string; + deptName: string; + userName: string; + ipaddr: string; + loginLocation: string; + browser: string; + os: string; + loginTime: number; +} diff --git a/src/api/monitor/operlog/index.ts b/src/api/monitor/operlog/index.ts new file mode 100644 index 0000000..7ac3453 --- /dev/null +++ b/src/api/monitor/operlog/index.ts @@ -0,0 +1,28 @@ +import request from '@/utils/request'; +import { OperLogQuery, OperLogVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询操作日志列表 +export function list(query: OperLogQuery): AxiosPromise { + return request({ + url: '/monitor/operlog/list', + method: 'get', + params: query + }); +} + +// 删除操作日志 +export function delOperlog(operId: string | number | Array) { + return request({ + url: '/monitor/operlog/' + operId, + method: 'delete' + }); +} + +// 清空操作日志 +export function cleanOperlog() { + return request({ + url: '/monitor/operlog/clean', + method: 'delete' + }); +} diff --git a/src/api/monitor/operlog/types.ts b/src/api/monitor/operlog/types.ts new file mode 100644 index 0000000..10f65c7 --- /dev/null +++ b/src/api/monitor/operlog/types.ts @@ -0,0 +1,53 @@ +export interface OperLogQuery extends PageQuery { + operIp: string; + title: string; + operName: string; + businessType: string; + status: string; + orderByColumn: string; + isAsc: string; +} + +export interface OperLogVO extends BaseEntity { + operId: string | number; + tenantId: string; + title: string; + businessType: number; + businessTypes: number[] | undefined; + method: string; + requestMethod: string; + operatorType: number; + operName: string; + deptName: string; + operUrl: string; + operIp: string; + operLocation: string; + operParam: string; + jsonResult: string; + status: number; + errorMsg: string; + operTime: string; + costTime: number; +} + +export interface OperLogForm { + operId: number | string | undefined; + tenantId: string | number | undefined; + title: string; + businessType: number; + businessTypes: number[] | undefined; + method: string; + requestMethod: string; + operatorType: number; + operName: string; + deptName: string; + operUrl: string; + operIp: string; + operLocation: string; + operParam: string; + jsonResult: string; + status: number; + errorMsg: string; + operTime: string; + costTime: number; +} diff --git a/src/api/system/client/index.ts b/src/api/system/client/index.ts new file mode 100644 index 0000000..06544da --- /dev/null +++ b/src/api/system/client/index.ts @@ -0,0 +1,80 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { ClientVO, ClientForm, ClientQuery } from '@/api/system/client/types'; + +/** + * 查询客户端管理列表 + * @param query + * @returns {*} + */ + +export const listClient = (query?: ClientQuery): AxiosPromise => { + return request({ + url: '/system/client/list', + method: 'get', + params: query + }); +}; + +/** + * 查询客户端管理详细 + * @param id + */ +export const getClient = (id: string | number): AxiosPromise => { + return request({ + url: '/system/client/' + id, + method: 'get' + }); +}; + +/** + * 新增客户端管理 + * @param data + */ +export const addClient = (data: ClientForm) => { + return request({ + url: '/system/client', + method: 'post', + data: data + }); +}; + +/** + * 修改客户端管理 + * @param data + */ +export const updateClient = (data: ClientForm) => { + return request({ + url: '/system/client', + method: 'put', + data: data + }); +}; + +/** + * 删除客户端管理 + * @param id + */ +export const delClient = (id: string | number | Array) => { + return request({ + url: '/system/client/' + id, + method: 'delete' + }); +}; + +/** + * 状态修改 + * @param id ID + * @param status 状态 + */ +export function changeStatus(id: number | string, status: string) { + const data = { + id, + status + }; + return request({ + url: '/system/client/changeStatus', + method: 'put', + data: data + }); +} diff --git a/src/api/system/client/types.ts b/src/api/system/client/types.ts new file mode 100644 index 0000000..d7cb926 --- /dev/null +++ b/src/api/system/client/types.ts @@ -0,0 +1,135 @@ +export interface ClientVO { + /** + * id + */ + id: string | number; + + /** + * 客户端id + */ + clientId: string | number; + + /** + * 客户端key + */ + clientKey: string; + + /** + * 客户端秘钥 + */ + clientSecret: string; + + /** + * 授权类型 + */ + grantTypeList: string[]; + + /** + * 设备类型 + */ + deviceType: string; + + /** + * token活跃超时时间 + */ + activeTimeout: number; + + /** + * token固定超时 + */ + timeout: number; + + /** + * 状态(0正常 1停用) + */ + status: string; +} + +export interface ClientForm extends BaseEntity { + /** + * id + */ + id?: string | number; + + /** + * 客户端id + */ + clientId?: string | number; + + /** + * 客户端key + */ + clientKey?: string; + + /** + * 客户端秘钥 + */ + clientSecret?: string; + + /** + * 授权类型 + */ + grantTypeList?: string[]; + + /** + * 设备类型 + */ + deviceType?: string; + + /** + * token活跃超时时间 + */ + activeTimeout?: number; + + /** + * token固定超时 + */ + timeout?: number; + + /** + * 状态(0正常 1停用) + */ + status?: string; +} + +export interface ClientQuery extends PageQuery { + /** + * 客户端id + */ + clientId?: string | number; + + /** + * 客户端key + */ + clientKey?: string; + + /** + * 客户端秘钥 + */ + clientSecret?: string; + + /** + * 授权类型 + */ + grantType?: string; + + /** + * 设备类型 + */ + deviceType?: string; + + /** + * token活跃超时时间 + */ + activeTimeout?: number; + + /** + * token固定超时 + */ + timeout?: number; + + /** + * 状态(0正常 1停用) + */ + status?: string; +} diff --git a/src/api/system/config/index.ts b/src/api/system/config/index.ts new file mode 100644 index 0000000..e52a256 --- /dev/null +++ b/src/api/system/config/index.ts @@ -0,0 +1,74 @@ +import request from '@/utils/request'; +import { ConfigForm, ConfigQuery, ConfigVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询参数列表 +export function listConfig(query: ConfigQuery): AxiosPromise { + return request({ + url: '/system/config/list', + method: 'get', + params: query + }); +} + +// 查询参数详细 +export function getConfig(configId: string | number): AxiosPromise { + return request({ + url: '/system/config/' + configId, + method: 'get' + }); +} + +// 根据参数键名查询参数值 +export function getConfigKey(configKey: string): AxiosPromise { + return request({ + url: '/system/config/configKey/' + configKey, + method: 'get' + }); +} + +// 新增参数配置 +export function addConfig(data: ConfigForm) { + return request({ + url: '/system/config', + method: 'post', + data: data + }); +} + +// 修改参数配置 +export function updateConfig(data: ConfigForm) { + return request({ + url: '/system/config', + method: 'put', + data: data + }); +} + +// 修改参数配置 +export function updateConfigByKey(key: string, value: any) { + return request({ + url: '/system/config/updateByKey', + method: 'put', + data: { + configKey: key, + configValue: value + } + }); +} + +// 删除参数配置 +export function delConfig(configId: string | number | Array) { + return request({ + url: '/system/config/' + configId, + method: 'delete' + }); +} + +// 刷新参数缓存 +export function refreshCache() { + return request({ + url: '/system/config/refreshCache', + method: 'delete' + }); +} diff --git a/src/api/system/config/types.ts b/src/api/system/config/types.ts new file mode 100644 index 0000000..b68f073 --- /dev/null +++ b/src/api/system/config/types.ts @@ -0,0 +1,23 @@ +export interface ConfigVO extends BaseEntity { + configId: number | string; + configName: string; + configKey: string; + configValue: string; + configType: string; + remark: string; +} + +export interface ConfigForm { + configId: number | string | undefined; + configName: string; + configKey: string; + configValue: string; + configType: string; + remark: string; +} + +export interface ConfigQuery extends PageQuery { + configName: string; + configKey: string; + configType: string; +} diff --git a/src/api/system/dept/index.ts b/src/api/system/dept/index.ts new file mode 100644 index 0000000..7e097fd --- /dev/null +++ b/src/api/system/dept/index.ts @@ -0,0 +1,62 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { DeptForm, DeptQuery, DeptVO } from './types'; + +// 查询部门列表 +export const listDept = (query?: DeptQuery) => { + return request({ + url: '/system/dept/list', + method: 'get', + params: query + }); +}; + +// 查询部门列表(排除节点) +export const listDeptExcludeChild = (deptId: string | number): AxiosPromise => { + return request({ + url: '/system/dept/list/exclude/' + deptId, + method: 'get' + }); +}; + +// 查询部门详细 +export const getDept = (deptId: string | number): AxiosPromise => { + return request({ + url: '/system/dept/' + deptId, + method: 'get' + }); +}; + +// 查询部门下拉树结构 +export const treeselect = (): AxiosPromise => { + return request({ + url: '/system/dept/treeselect', + method: 'get' + }); +}; + +// 新增部门 +export const addDept = (data: DeptForm) => { + return request({ + url: '/system/dept', + method: 'post', + data: data + }); +}; + +// 修改部门 +export const updateDept = (data: DeptForm) => { + return request({ + url: '/system/dept', + method: 'put', + data: data + }); +}; + +// 删除部门 +export const delDept = (deptId: number | string) => { + return request({ + url: '/system/dept/' + deptId, + method: 'delete' + }); +}; diff --git a/src/api/system/dept/types.ts b/src/api/system/dept/types.ts new file mode 100644 index 0000000..867dbde --- /dev/null +++ b/src/api/system/dept/types.ts @@ -0,0 +1,45 @@ +/** + * 部门查询参数 + */ +export interface DeptQuery extends PageQuery { + deptName?: string; + status?: number; +} + +/** + * 部门类型 + */ +export interface DeptVO extends BaseEntity { + id: number | string; + parentName: string; + parentId: number | string; + children: DeptVO[]; + deptId: number | string; + deptName: string; + orderNum: number; + leader: string; + phone: string; + email: string; + status: string; + delFlag: string; + ancestors: string; + menuId: string | number; +} + +/** + * 部门表单类型 + */ +export interface DeptForm { + parentName?: string; + parentId?: number | string; + children?: DeptForm[]; + deptId?: number | string; + deptName?: string; + orderNum?: number; + leader?: string; + phone?: string; + email?: string; + status?: string; + delFlag?: string; + ancestors?: string; +} diff --git a/src/api/system/dict/data/index.ts b/src/api/system/dict/data/index.ts new file mode 100644 index 0000000..7692abc --- /dev/null +++ b/src/api/system/dict/data/index.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { DictDataForm, DictDataQuery, DictDataVO } from './types'; +// 根据字典类型查询字典数据信息 +export function getDicts(dictType: string): AxiosPromise { + return request({ + url: '/system/dict/data/type/' + dictType, + method: 'get' + }); +} + +// 查询字典数据列表 +export function listData(query: DictDataQuery): AxiosPromise { + return request({ + url: '/system/dict/data/list', + method: 'get', + params: query + }); +} + +// 查询字典数据详细 +export function getData(dictCode: string | number): AxiosPromise { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'get' + }); +} + +// 新增字典数据 +export function addData(data: DictDataForm) { + return request({ + url: '/system/dict/data', + method: 'post', + data: data + }); +} + +// 修改字典数据 +export function updateData(data: DictDataForm) { + return request({ + url: '/system/dict/data', + method: 'put', + data: data + }); +} + +// 删除字典数据 +export function delData(dictCode: string | number | Array) { + return request({ + url: '/system/dict/data/' + dictCode, + method: 'delete' + }); +} diff --git a/src/api/system/dict/data/types.ts b/src/api/system/dict/data/types.ts new file mode 100644 index 0000000..e4abb9b --- /dev/null +++ b/src/api/system/dict/data/types.ts @@ -0,0 +1,26 @@ +export interface DictDataQuery extends PageQuery { + dictName: string; + dictType: string; + dictLabel: string; +} + +export interface DictDataVO extends BaseEntity { + dictCode: string; + dictLabel: string; + dictValue: string; + cssClass: string; + listClass: ElTagType; + dictSort: number; + remark: string; +} + +export interface DictDataForm { + dictType?: string; + dictCode: string | undefined; + dictLabel: string; + dictValue: string; + cssClass: string; + listClass: ElTagType; + dictSort: number; + remark: string; +} diff --git a/src/api/system/dict/type/index.ts b/src/api/system/dict/type/index.ts new file mode 100644 index 0000000..7dc3d66 --- /dev/null +++ b/src/api/system/dict/type/index.ts @@ -0,0 +1,62 @@ +import request from '@/utils/request'; +import { DictTypeForm, DictTypeVO, DictTypeQuery } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询字典类型列表 +export function listType(query: DictTypeQuery): AxiosPromise { + return request({ + url: '/system/dict/type/list', + method: 'get', + params: query + }); +} + +// 查询字典类型详细 +export function getType(dictId: number | string): AxiosPromise { + return request({ + url: '/system/dict/type/' + dictId, + method: 'get' + }); +} + +// 新增字典类型 +export function addType(data: DictTypeForm) { + return request({ + url: '/system/dict/type', + method: 'post', + data: data + }); +} + +// 修改字典类型 +export function updateType(data: DictTypeForm) { + return request({ + url: '/system/dict/type', + method: 'put', + data: data + }); +} + +// 删除字典类型 +export function delType(dictId: string | number | Array) { + return request({ + url: '/system/dict/type/' + dictId, + method: 'delete' + }); +} + +// 刷新字典缓存 +export function refreshCache() { + return request({ + url: '/system/dict/type/refreshCache', + method: 'delete' + }); +} + +// 获取字典选择框列表 +export function optionselect(): AxiosPromise { + return request({ + url: '/system/dict/type/optionselect', + method: 'get' + }); +} diff --git a/src/api/system/dict/type/types.ts b/src/api/system/dict/type/types.ts new file mode 100644 index 0000000..4987dbf --- /dev/null +++ b/src/api/system/dict/type/types.ts @@ -0,0 +1,18 @@ +export interface DictTypeVO extends BaseEntity { + dictId: number | string; + dictName: string; + dictType: string; + remark: string; +} + +export interface DictTypeForm { + dictId: number | string | undefined; + dictName: string; + dictType: string; + remark: string; +} + +export interface DictTypeQuery extends PageQuery { + dictName: string; + dictType: string; +} diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts new file mode 100644 index 0000000..7a0cf74 --- /dev/null +++ b/src/api/system/menu/index.ts @@ -0,0 +1,70 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { MenuQuery, MenuVO, MenuForm, MenuTreeOption, RoleMenuTree } from './types'; + +// 查询菜单列表 +export const listMenu = (query?: MenuQuery): AxiosPromise => { + return request({ + url: '/system/menu/list', + method: 'get', + params: query + }); +}; + +// 查询菜单详细 +export const getMenu = (menuId: string | number): AxiosPromise => { + return request({ + url: '/system/menu/' + menuId, + method: 'get' + }); +}; + +// 查询菜单下拉树结构 +export const treeselect = (): AxiosPromise => { + return request({ + url: '/system/menu/treeselect', + method: 'get' + }); +}; + +// 根据角色ID查询菜单下拉树结构 +export const roleMenuTreeselect = (roleId: string | number): AxiosPromise => { + return request({ + url: '/system/menu/roleMenuTreeselect/' + roleId, + method: 'get' + }); +}; + +// 根据角色ID查询菜单下拉树结构 +export const tenantPackageMenuTreeselect = (packageId: string | number): AxiosPromise => { + return request({ + url: '/system/menu/tenantPackageMenuTreeselect/' + packageId, + method: 'get' + }); +}; + +// 新增菜单 +export const addMenu = (data: MenuForm) => { + return request({ + url: '/system/menu', + method: 'post', + data: data + }); +}; + +// 修改菜单 +export const updateMenu = (data: MenuForm) => { + return request({ + url: '/system/menu', + method: 'put', + data: data + }); +}; + +// 删除菜单 +export const delMenu = (menuId: string | number) => { + return request({ + url: '/system/menu/' + menuId, + method: 'delete' + }); +}; diff --git a/src/api/system/menu/types.ts b/src/api/system/menu/types.ts new file mode 100644 index 0000000..ca19840 --- /dev/null +++ b/src/api/system/menu/types.ts @@ -0,0 +1,69 @@ +import { MenuTypeEnum } from '@/enums/MenuTypeEnum'; + +/** + * 菜单树形结构类型 + */ +export interface MenuTreeOption { + id: string | number; + label: string; + parentId: string | number; + weight: number; + children?: MenuTreeOption[]; +} + +export interface RoleMenuTree { + menus: MenuTreeOption[]; + checkedKeys: string[]; +} + +/** + * 菜单查询参数类型 + */ +export interface MenuQuery { + keywords?: string; + menuName?: string; + status?: string; +} + +/** + * 菜单视图对象类型 + */ +export interface MenuVO extends BaseEntity { + parentName: string; + parentId: string | number; + children: MenuVO[]; + menuId: string | number; + menuName: string; + orderNum: number; + path: string; + component: string; + queryParam: string; + isFrame: string; + isCache: string; + menuType: MenuTypeEnum; + visible: string; + status: string; + icon: string; + remark: string; +} + +export interface MenuForm { + parentName?: string; + parentId?: string | number; + children?: MenuForm[]; + menuId?: string | number; + menuName: string; + orderNum: number; + path: string; + component?: string; + queryParam?: string; + isFrame?: string; + isCache?: string; + menuType?: MenuTypeEnum; + visible?: string; + status?: string; + icon?: string; + remark?: string; + query?: string; + perms?: string; +} diff --git a/src/api/system/notice/index.ts b/src/api/system/notice/index.ts new file mode 100644 index 0000000..285d1f4 --- /dev/null +++ b/src/api/system/notice/index.ts @@ -0,0 +1,45 @@ +import request from '@/utils/request'; +import { NoticeForm, NoticeQuery, NoticeVO } from './types'; +import { AxiosPromise } from 'axios'; +// 查询公告列表 +export function listNotice(query: NoticeQuery): AxiosPromise { + return request({ + url: '/system/notice/list', + method: 'get', + params: query + }); +} + +// 查询公告详细 +export function getNotice(noticeId: string | number): AxiosPromise { + return request({ + url: '/system/notice/' + noticeId, + method: 'get' + }); +} + +// 新增公告 +export function addNotice(data: NoticeForm) { + return request({ + url: '/system/notice', + method: 'post', + data: data + }); +} + +// 修改公告 +export function updateNotice(data: NoticeForm) { + return request({ + url: '/system/notice', + method: 'put', + data: data + }); +} + +// 删除公告 +export function delNotice(noticeId: string | number | Array) { + return request({ + url: '/system/notice/' + noticeId, + method: 'delete' + }); +} diff --git a/src/api/system/notice/types.ts b/src/api/system/notice/types.ts new file mode 100644 index 0000000..abfd5b2 --- /dev/null +++ b/src/api/system/notice/types.ts @@ -0,0 +1,26 @@ +export interface NoticeVO extends BaseEntity { + noticeId: number; + noticeTitle: string; + noticeType: string; + noticeContent: string; + status: string; + remark: string; + createByName: string; +} + +export interface NoticeQuery extends PageQuery { + noticeTitle: string; + createByName: string; + status: string; + noticeType: string; +} + +export interface NoticeForm { + noticeId: number | string | undefined; + noticeTitle: string; + noticeType: string; + noticeContent: string; + status: string; + remark: string; + createByName: string; +} diff --git a/src/api/system/oss/index.ts b/src/api/system/oss/index.ts new file mode 100644 index 0000000..4472112 --- /dev/null +++ b/src/api/system/oss/index.ts @@ -0,0 +1,28 @@ +import request from '@/utils/request'; +import { OssQuery, OssVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询OSS对象存储列表 +export function listOss(query: OssQuery): AxiosPromise { + return request({ + url: '/resource/oss/list', + method: 'get', + params: query + }); +} + +// 查询OSS对象基于id串 +export function listByIds(ossId: string | number): AxiosPromise { + return request({ + url: '/resource/oss/listByIds/' + ossId, + method: 'get' + }); +} + +// 删除OSS对象存储 +export function delOss(ossId: string | number | Array) { + return request({ + url: '/resource/oss/' + ossId, + method: 'delete' + }); +} diff --git a/src/api/system/oss/types.ts b/src/api/system/oss/types.ts new file mode 100644 index 0000000..bc0bc1f --- /dev/null +++ b/src/api/system/oss/types.ts @@ -0,0 +1,22 @@ +export interface OssVO extends BaseEntity { + ossId: string | number; + fileName: string; + originalName: string; + fileSuffix: string; + url: string; + createByName: string; + service: string; +} + +export interface OssQuery extends PageQuery { + fileName: string; + originalName: string; + fileSuffix: string; + createTime: string; + service: string; + orderByColumn: string; + isAsc: string; +} +export interface OssForm { + file: undefined | string; +} diff --git a/src/api/system/ossConfig/index.ts b/src/api/system/ossConfig/index.ts new file mode 100644 index 0000000..d0faefe --- /dev/null +++ b/src/api/system/ossConfig/index.ts @@ -0,0 +1,60 @@ +import request from '@/utils/request'; +import { OssConfigForm, OssConfigQuery, OssConfigVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询对象存储配置列表 +export function listOssConfig(query: OssConfigQuery): AxiosPromise { + return request({ + url: '/resource/oss/config/list', + method: 'get', + params: query + }); +} + +// 查询对象存储配置详细 +export function getOssConfig(ossConfigId: string | number): AxiosPromise { + return request({ + url: '/resource/oss/config/' + ossConfigId, + method: 'get' + }); +} + +// 新增对象存储配置 +export function addOssConfig(data: OssConfigForm) { + return request({ + url: '/resource/oss/config', + method: 'post', + data: data + }); +} + +// 修改对象存储配置 +export function updateOssConfig(data: OssConfigForm) { + return request({ + url: '/resource/oss/config', + method: 'put', + data: data + }); +} + +// 删除对象存储配置 +export function delOssConfig(ossConfigId: string | number | Array) { + return request({ + url: '/resource/oss/config/' + ossConfigId, + method: 'delete' + }); +} + +// 对象存储状态修改 +export function changeOssConfigStatus(ossConfigId: string | number, status: string, configKey: string) { + const data = { + ossConfigId, + status, + configKey + }; + return request({ + url: '/resource/oss/config/changeStatus', + method: 'put', + data: data + }); +} diff --git a/src/api/system/ossConfig/types.ts b/src/api/system/ossConfig/types.ts new file mode 100644 index 0000000..d227bd0 --- /dev/null +++ b/src/api/system/ossConfig/types.ts @@ -0,0 +1,38 @@ +export interface OssConfigVO extends BaseEntity { + ossConfigId: number | string; + configKey: string; + accessKey: string; + secretKey: string; + bucketName: string; + prefix: string; + endpoint: string; + domain: string; + isHttps: string; + region: string; + status: string; + ext1: string; + remark: string; + accessPolicy: string; +} + +export interface OssConfigQuery extends PageQuery { + configKey: string; + bucketName: string; + status: string; +} + +export interface OssConfigForm { + ossConfigId: string | number | undefined; + configKey: string; + accessKey: string; + secretKey: string; + bucketName: string; + prefix: string; + endpoint: string; + domain: string; + isHttps: string; + accessPolicy: string; + region: string; + status: string; + remark: string; +} diff --git a/src/api/system/post/index.ts b/src/api/system/post/index.ts new file mode 100644 index 0000000..3e523ab --- /dev/null +++ b/src/api/system/post/index.ts @@ -0,0 +1,46 @@ +import request from '@/utils/request'; +import { PostForm, PostQuery, PostVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询岗位列表 +export function listPost(query: PostQuery): AxiosPromise { + return request({ + url: '/system/post/list', + method: 'get', + params: query + }); +} + +// 查询岗位详细 +export function getPost(postId: string | number): AxiosPromise { + return request({ + url: '/system/post/' + postId, + method: 'get' + }); +} + +// 新增岗位 +export function addPost(data: PostForm) { + return request({ + url: '/system/post', + method: 'post', + data: data + }); +} + +// 修改岗位 +export function updatePost(data: PostForm) { + return request({ + url: '/system/post', + method: 'put', + data: data + }); +} + +// 删除岗位 +export function delPost(postId: string | number | (string | number)[]) { + return request({ + url: '/system/post/' + postId, + method: 'delete' + }); +} diff --git a/src/api/system/post/types.ts b/src/api/system/post/types.ts new file mode 100644 index 0000000..84e6786 --- /dev/null +++ b/src/api/system/post/types.ts @@ -0,0 +1,23 @@ +export interface PostVO extends BaseEntity { + postId: number | string; + postCode: string; + postName: string; + postSort: number; + status: string; + remark: string; +} + +export interface PostForm { + postId: number | string | undefined; + postCode: string; + postName: string; + postSort: number; + status: string; + remark: string; +} + +export interface PostQuery extends PageQuery { + postCode: string; + postName: string; + status: string; +} diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts new file mode 100644 index 0000000..4e8b612 --- /dev/null +++ b/src/api/system/role/index.ts @@ -0,0 +1,144 @@ +import { UserVO } from '@/api/system/user/types'; +import { UserQuery } from '@/api/system/user/types'; +import { AxiosPromise } from 'axios'; +import { RoleQuery, RoleVO, RoleDeptTree } from './types'; +import request from '@/utils/request'; + +export const listRole = (query: RoleQuery): AxiosPromise => { + return request({ + url: '/system/role/list', + method: 'get', + params: query + }); +}; + +/** + * 查询角色详细 + */ +export const getRole = (roleId: string | number): AxiosPromise => { + return request({ + url: '/system/role/' + roleId, + method: 'get' + }); +}; + +/** + * 新增角色 + */ +export const addRole = (data: any) => { + return request({ + url: '/system/role', + method: 'post', + data: data + }); +}; + +/** + * 修改角色 + * @param data + */ +export const updateRole = (data: any) => { + return request({ + url: '/system/role', + method: 'put', + data: data + }); +}; + +/** + * 角色数据权限 + */ +export const dataScope = (data: any) => { + return request({ + url: '/system/role/dataScope', + method: 'put', + data: data + }); +}; + +/** + * 角色状态修改 + */ +export const changeRoleStatus = (roleId: string | number, status: string) => { + const data = { + roleId, + status + }; + return request({ + url: '/system/role/changeStatus', + method: 'put', + data: data + }); +}; + +/** + * 删除角色 + */ +export const delRole = (roleId: Array | string | number) => { + return request({ + url: '/system/role/' + roleId, + method: 'delete' + }); +}; + +/** + * 查询角色已授权用户列表 + */ +export const allocatedUserList = (query: UserQuery): AxiosPromise => { + return request({ + url: '/system/role/authUser/allocatedList', + method: 'get', + params: query + }); +}; + +/** + * 查询角色未授权用户列表 + */ +export const unallocatedUserList = (query: UserQuery): AxiosPromise => { + return request({ + url: '/system/role/authUser/unallocatedList', + method: 'get', + params: query + }); +}; + +/** + * 取消用户授权角色 + */ +export const authUserCancel = (data: any) => { + return request({ + url: '/system/role/authUser/cancel', + method: 'put', + data: data + }); +}; + +/** + * 批量取消用户授权角色 + */ +export const authUserCancelAll = (data: any) => { + return request({ + url: '/system/role/authUser/cancelAll', + method: 'put', + params: data + }); +}; + +/** + * 授权用户选择 + */ +export const authUserSelectAll = (data: any) => { + return request({ + url: '/system/role/authUser/selectAll', + method: 'put', + params: data + }); +}; +// 根据角色ID查询部门树结构 +export const deptTreeSelect = (roleId: string | number): AxiosPromise => { + return request({ + url: '/system/role/deptTree/' + roleId, + method: 'get' + }); +}; diff --git a/src/api/system/role/types.ts b/src/api/system/role/types.ts new file mode 100644 index 0000000..7dbb6ff --- /dev/null +++ b/src/api/system/role/types.ts @@ -0,0 +1,52 @@ +/** + * 菜单树形结构类型 + */ +export interface DeptTreeOption { + id: string; + label: string; + parentId: string; + weight: number; + children?: DeptTreeOption[]; +} + +export interface RoleDeptTree { + checkedKeys: string[]; + depts: DeptTreeOption[]; +} + +export interface RoleVO extends BaseEntity { + roleId: string | number; + roleName: string; + roleKey: string; + roleSort: number; + dataScope: string; + menuCheckStrictly: boolean; + deptCheckStrictly: boolean; + status: string; + delFlag: string; + remark?: any; + flag: boolean; + menuIds?: Array; + deptIds?: Array; + admin: boolean; +} + +export interface RoleQuery extends PageQuery { + roleName: string; + roleKey: string; + status: string; +} + +export interface RoleForm { + roleName: string; + roleKey: string; + roleSort: number; + status: string; + menuCheckStrictly: boolean; + deptCheckStrictly: boolean; + remark: string; + dataScope?: string; + roleId: string | undefined; + menuIds: Array; + deptIds: Array; +} diff --git a/src/api/system/social/auth.ts b/src/api/system/social/auth.ts new file mode 100644 index 0000000..17a46d3 --- /dev/null +++ b/src/api/system/social/auth.ts @@ -0,0 +1,24 @@ +import request from '@/utils/request'; + +// 绑定账号 +export function authBinding(source: string) { + return request({ + url: '/auth/binding/' + source, + method: 'get' + }); +} + +// 解绑账号 +export function authUnlock(authId: string) { + return request({ + url: '/auth/unlock/' + authId, + method: 'delete' + }); +} +//获取授权列表 +export function getAuthList() { + return request({ + url: '/system/social/list', + method: 'get' + }); +} diff --git a/src/api/system/tenant/index.ts b/src/api/system/tenant/index.ts new file mode 100644 index 0000000..4531a0a --- /dev/null +++ b/src/api/system/tenant/index.ts @@ -0,0 +1,92 @@ +import request from '@/utils/request'; +import { TenantForm, TenantQuery, TenantVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询租户列表 +export function listTenant(query: TenantQuery): AxiosPromise { + return request({ + url: '/system/tenant/list', + method: 'get', + params: query + }); +} + +// 查询租户详细 +export function getTenant(id: string | number): AxiosPromise { + return request({ + url: '/system/tenant/' + id, + method: 'get' + }); +} + +// 新增租户 +export function addTenant(data: TenantForm) { + return request({ + url: '/system/tenant', + method: 'post', + headers: { + isEncrypt: true + }, + data: data + }); +} + +// 修改租户 +export function updateTenant(data: TenantForm) { + return request({ + url: '/system/tenant', + method: 'put', + data: data + }); +} + +// 租户状态修改 +export function changeTenantStatus(id: string | number, tenantId: string | number, status: string) { + const data = { + id, + tenantId, + status + }; + return request({ + url: '/system/tenant/changeStatus', + method: 'put', + data: data + }); +} + +// 删除租户 +export function delTenant(id: string | number | Array) { + return request({ + url: '/system/tenant/' + id, + method: 'delete' + }); +} + +// 动态切换租户 +export function dynamicTenant(tenantId: string | number) { + return request({ + url: '/system/tenant/dynamic/' + tenantId, + method: 'get' + }); +} + +// 清除动态租户 +export function dynamicClear() { + return request({ + url: '/system/tenant/dynamic/clear', + method: 'get' + }); +} + +// 同步租户套餐 +export function syncTenantPackage(tenantId: string | number, packageId: string | number) { + const data = { + tenantId, + packageId + }; + return request({ + url: '/system/tenant/syncTenantPackage', + method: 'get', + params: data + }); +} diff --git a/src/api/system/tenant/types.ts b/src/api/system/tenant/types.ts new file mode 100644 index 0000000..25395fa --- /dev/null +++ b/src/api/system/tenant/types.ts @@ -0,0 +1,46 @@ +export interface TenantVO extends BaseEntity { + id: number | string; + tenantId: number | string; + username: string; + contactUserName: string; + contactPhone: string; + companyName: string; + licenseNumber: string; + address: string; + domain: string; + intro: string; + remark: string; + packageId: string | number; + expireTime: string; + accountCount: number; + status: string; +} + +export interface TenantQuery extends PageQuery { + tenantId: string | number; + + contactUserName: string; + + contactPhone: string; + + companyName: string; +} + +export interface TenantForm { + id: number | string | undefined; + tenantId: number | string | undefined; + username: string; + password: string; + contactUserName: string; + contactPhone: string; + companyName: string; + licenseNumber: string; + domain: string; + address: string; + intro: string; + remark: string; + packageId: string | number; + expireTime: string; + accountCount: number; + status: string; +} diff --git a/src/api/system/tenantPackage/index.ts b/src/api/system/tenantPackage/index.ts new file mode 100644 index 0000000..70d0dab --- /dev/null +++ b/src/api/system/tenantPackage/index.ts @@ -0,0 +1,67 @@ +import request from '@/utils/request'; +import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询租户套餐列表 +export function listTenantPackage(query?: TenantPkgQuery): AxiosPromise { + return request({ + url: '/system/tenant/package/list', + method: 'get', + params: query + }); +} + +// 查询租户套餐下拉选列表 +export function selectTenantPackage(): AxiosPromise { + return request({ + url: '/system/tenant/package/selectList', + method: 'get' + }); +} + +// 查询租户套餐详细 +export function getTenantPackage(packageId: string | number): AxiosPromise { + return request({ + url: '/system/tenant/package/' + packageId, + method: 'get' + }); +} + +// 新增租户套餐 +export function addTenantPackage(data: TenantPkgForm) { + return request({ + url: '/system/tenant/package', + method: 'post', + data: data + }); +} + +// 修改租户套餐 +export function updateTenantPackage(data: TenantPkgForm) { + return request({ + url: '/system/tenant/package', + method: 'put', + data: data + }); +} + +// 租户套餐状态修改 +export function changePackageStatus(packageId: number | string, status: string) { + const data = { + packageId, + status + }; + return request({ + url: '/system/tenant/package/changeStatus', + method: 'put', + data: data + }); +} + +// 删除租户套餐 +export function delTenantPackage(packageId: string | number | Array) { + return request({ + url: '/system/tenant/package/' + packageId, + method: 'delete' + }); +} diff --git a/src/api/system/tenantPackage/types.ts b/src/api/system/tenantPackage/types.ts new file mode 100644 index 0000000..c24b8fc --- /dev/null +++ b/src/api/system/tenantPackage/types.ts @@ -0,0 +1,20 @@ +export interface TenantPkgVO extends BaseEntity { + packageId: string | number; + packageName: string; + menuIds: string; + remark: string; + menuCheckStrictly: boolean; + status: string; +} + +export interface TenantPkgQuery extends PageQuery { + packageName: string; +} + +export interface TenantPkgForm { + packageId: string | number | undefined; + packageName: string; + menuIds: string; + remark: string; + menuCheckStrictly: boolean; +} diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts new file mode 100644 index 0000000..fdcafc2 --- /dev/null +++ b/src/api/system/user/index.ts @@ -0,0 +1,215 @@ +import { DeptVO } from './../dept/types'; +import { RoleVO } from '@/api/system/role/types'; +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { UserForm, UserQuery, UserVO, UserInfoVO } from './types'; +import { parseStrEmpty } from '@/utils/ruoyi'; + +/** + * 查询用户列表 + * @param query + */ +export const listUser = (query: UserQuery): AxiosPromise => { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }); +}; + +/** + * 获取用户详情 + * @param userId + */ +export const getUser = (userId?: string | number): AxiosPromise => { + return request({ + url: '/system/user/' + parseStrEmpty(userId), + method: 'get' + }); +}; + +/** + * 新增用户 + */ +export const addUser = (data: UserForm) => { + return request({ + url: '/system/user', + method: 'post', + data: data + }); +}; + +/** + * 修改用户 + */ +export const updateUser = (data: UserForm) => { + return request({ + url: '/system/user', + method: 'put', + data: data + }); +}; + +/** + * 删除用户 + * @param userId 用户ID + */ +export const delUser = (userId: Array | string | number) => { + return request({ + url: '/system/user/' + userId, + method: 'delete' + }); +}; + +/** + * 用户密码重置 + * @param userId 用户ID + * @param password 密码 + */ +export const resetUserPwd = (userId: string | number, password: string) => { + const data = { + userId, + password + }; + return request({ + url: '/system/user/resetPwd', + method: 'put', + headers: { + isEncrypt: true + }, + data: data + }); +}; + +/** + * 用户状态修改 + * @param userId 用户ID + * @param status 用户状态 + */ +export const changeUserStatus = (userId: number | string, status: string) => { + const data = { + userId, + status + }; + return request({ + url: '/system/user/changeStatus', + method: 'put', + data: data + }); +}; + +/** + * 查询用户个人信息 + */ +export const getUserProfile = (): AxiosPromise => { + return request({ + url: '/system/user/profile', + method: 'get' + }); +}; + +/** + * 修改用户个人信息 + * @param data 用户信息 + */ +export const updateUserProfile = (data: UserForm) => { + return request({ + url: '/system/user/profile', + method: 'put', + data: data + }); +}; + +/** + * 用户密码重置 + * @param oldPassword 旧密码 + * @param newPassword 新密码 + */ +export const updateUserPwd = (oldPassword: string, newPassword: string) => { + const data = { + oldPassword, + newPassword + }; + return request({ + url: '/system/user/profile/updatePwd', + method: 'put', + headers: { + isEncrypt: true + }, + data: data + }); +}; + +/** + * 用户头像上传 + * @param data 头像文件 + */ +export const uploadAvatar = (data: FormData) => { + return request({ + url: '/system/user/profile/avatar', + method: 'post', + data: data + }); +}; + +/** + * 查询授权角色 + * @param userId 用户ID + */ +export const getAuthRole = (userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> => { + return request({ + url: '/system/user/authRole/' + userId, + method: 'get' + }); +}; + +/** + * 保存授权角色 + * @param data 用户ID + */ +export const updateAuthRole = (data: { userId: string; roleIds: string }) => { + return request({ + url: '/system/user/authRole', + method: 'put', + params: data + }); +}; + +/** + * 查询当前部门的所有用户信息 + * @param deptId + */ +export const listUserByDeptId = (deptId: string | number): AxiosPromise => { + return request({ + url: '/system/user/list/dept/' + deptId, + method: 'get' + }); +}; + +/** + * 查询部门下拉树结构 + */ +export const deptTreeSelect = (): AxiosPromise => { + return request({ + url: '/system/user/deptTree', + method: 'get' + }); +}; + +export default { + listUser, + getUser, + addUser, + updateUser, + delUser, + resetUserPwd, + changeUserStatus, + getUserProfile, + updateUserProfile, + updateUserPwd, + uploadAvatar, + getAuthRole, + updateAuthRole, + deptTreeSelect, + listUserByDeptId +}; diff --git a/src/api/system/user/types.ts b/src/api/system/user/types.ts new file mode 100644 index 0000000..77e9c32 --- /dev/null +++ b/src/api/system/user/types.ts @@ -0,0 +1,84 @@ +import { DeptVO } from './../dept/types'; +import { RoleVO } from '@/api/system/role/types'; +import { PostVO } from '@/api/system/post/types'; + +/** + * 用户信息 + */ +export interface UserInfo { + user: UserVO; + roles: string[]; + permissions: string[]; +} + +/** + * 用户查询对象类型 + */ +export interface UserQuery extends PageQuery { + userName?: string; + phonenumber?: string; + status?: string; + deptId?: string | number; + roleId?: string | number; +} + +/** + * 用户返回对象 + */ +export interface UserVO extends BaseEntity { + userId: string | number; + deptId: number; + userName: string; + nickName: string; + userType: string; + email: string; + phonenumber: string; + sex: string; + avatar: string; + status: string; + delFlag: string; + loginIp: string; + loginDate: string; + remark: string; + dept: DeptVO; + roles: RoleVO[]; + roleIds: any; + postIds: any; + roleId: any; + admin: boolean; +} + +/** + * 用户表单类型 + */ +export interface UserForm { + id?: string; + userId?: string; + deptId?: number; + userName: string; + nickName?: string; + password: string; + phonenumber?: string; + email?: string; + sex?: string; + status: string; + remark?: string; + postIds: string[]; + roleIds: string[]; +} + +export interface UserInfoVO { + user: UserVO; + roles: RoleVO[]; + roleIds: string[]; + posts: PostVO[]; + postIds: string[]; + roleGroup: string; + postGroup: string; +} + +export interface ResetPwdForm { + oldPassword: string; + newPassword: string; + confirmPassword: string; +} diff --git a/src/api/tool/gen/index.ts b/src/api/tool/gen/index.ts new file mode 100644 index 0000000..b91df6a --- /dev/null +++ b/src/api/tool/gen/index.ts @@ -0,0 +1,86 @@ +import request from '@/utils/request'; +import { DbTableQuery, DbTableVO, TableQuery, TableVO, GenTableVO, DbTableForm } from './types'; +import { AxiosPromise } from 'axios'; + +// 查询生成表数据 +export const listTable = (query: TableQuery): AxiosPromise => { + return request({ + url: '/tool/gen/list', + method: 'get', + params: query + }); +}; +// 查询db数据库列表 +export const listDbTable = (query: DbTableQuery): AxiosPromise => { + return request({ + url: '/tool/gen/db/list', + method: 'get', + params: query + }); +}; + +// 查询表详细信息 +export const getGenTable = (tableId: string | number): AxiosPromise => { + return request({ + url: '/tool/gen/' + tableId, + method: 'get' + }); +}; + +// 修改代码生成信息 +export const updateGenTable = (data: DbTableForm) => { + return request({ + url: '/tool/gen', + method: 'put', + data: data + }); +}; + +// 导入表 +export const importTable = (data: { tables: string; dataName: string }) => { + return request({ + url: '/tool/gen/importTable', + method: 'post', + params: data + }); +}; + +// 预览生成代码 +export const previewTable = (tableId: string | number) => { + return request({ + url: '/tool/gen/preview/' + tableId, + method: 'get' + }); +}; + +// 删除表数据 +export const delTable = (tableId: string | number | Array) => { + return request({ + url: '/tool/gen/' + tableId, + method: 'delete' + }); +}; + +// 生成代码(自定义路径) +export const genCode = (tableId: string | number) => { + return request({ + url: '/tool/gen/genCode/' + tableId, + method: 'get' + }); +}; + +// 同步数据库 +export const synchDb = (tableId: string | number) => { + return request({ + url: '/tool/gen/synchDb/' + tableId, + method: 'get' + }); +}; + +// 获取数据源名称 +export const getDataNames = () => { + return request({ + url: '/tool/gen/getDataNames', + method: 'get' + }); +}; diff --git a/src/api/tool/gen/types.ts b/src/api/tool/gen/types.ts new file mode 100644 index 0000000..7f7b62f --- /dev/null +++ b/src/api/tool/gen/types.ts @@ -0,0 +1,180 @@ +export interface TableVO extends BaseEntity { + createDept: number | string; + tableId: string | number; + dataName: string; + tableName: string; + tableComment: string; + subTableName?: any; + subTableFkName?: any; + className: string; + tplCategory: string; + packageName: string; + moduleName: string; + businessName: string; + functionName: string; + functionAuthor: string; + genType: string; + genPath: string; + pkColumn?: any; + columns?: any; + options?: any; + remark?: any; + treeCode?: any; + treeParentCode?: any; + treeName?: any; + menuIds?: any; + parentMenuId?: any; + parentMenuName?: any; + tree: boolean; + crud: boolean; +} + +export interface TableQuery extends PageQuery { + tableName: string; + tableComment: string; + dataName: string; +} + +export interface DbColumnVO extends BaseEntity { + createDept?: any; + columnId?: any; + tableId?: any; + columnName?: any; + columnComment?: any; + columnType?: any; + javaType?: any; + javaField?: any; + isPk?: any; + isIncrement?: any; + isRequired?: any; + isInsert?: any; + isEdit?: any; + isList?: any; + isQuery?: any; + queryType?: any; + htmlType?: any; + dictType?: any; + sort?: any; + increment: boolean; + capJavaField?: any; + usableColumn: boolean; + superColumn: boolean; + list: boolean; + pk: boolean; + insert: boolean; + edit: boolean; + query: boolean; + required: boolean; +} + +export interface DbTableVO { + createDept?: any; + tableId?: any; + tableName: string; + tableComment: string; + subTableName?: any; + subTableFkName?: any; + className?: any; + tplCategory?: any; + packageName?: any; + moduleName?: any; + businessName?: any; + functionName?: any; + functionAuthor?: any; + genType?: any; + genPath?: any; + pkColumn?: any; + columns: DbColumnVO[]; + options?: any; + remark?: any; + treeCode?: any; + treeParentCode?: any; + treeName?: any; + menuIds?: any; + parentMenuId?: any; + parentMenuName?: any; + tree: boolean; + crud: boolean; +} + +export interface DbTableQuery extends PageQuery { + dataName: string; + tableName: string; + tableComment: string; +} + +export interface GenTableVO { + info: DbTableVO; + rows: DbColumnVO[]; + tables: DbTableVO[]; +} + +export interface DbColumnForm extends BaseEntity { + createDept: number; + columnId: string; + tableId: string; + columnName: string; + columnComment: string; + columnType: string; + javaType: string; + javaField: string; + isPk: string; + isIncrement: string; + isRequired: string; + isInsert?: any; + isEdit: string; + isList: string; + isQuery?: any; + queryType: string; + htmlType: string; + dictType: string; + sort: number; + increment: boolean; + capJavaField: string; + usableColumn: boolean; + superColumn: boolean; + list: boolean; + pk: boolean; + insert: boolean; + edit: boolean; + query: boolean; + required: boolean; +} + +export interface DbParamForm { + treeCode?: any; + treeName?: any; + treeParentCode?: any; + parentMenuId: string; +} + +export interface DbTableForm extends BaseEntity { + createDept?: any; + tableId: string | string; + tableName: string; + tableComment: string; + subTableName?: any; + subTableFkName?: any; + className: string; + tplCategory: string; + packageName: string; + moduleName: string; + businessName: string; + functionName: string; + functionAuthor: string; + genType: string; + genPath: string; + pkColumn?: any; + columns: DbColumnForm[]; + options: string; + remark?: any; + treeCode?: any; + treeParentCode?: any; + treeName?: any; + menuIds?: any; + parentMenuId: string; + parentMenuName?: any; + tree: boolean; + crud: boolean; + params: DbParamForm; +} diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..617286c --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,59 @@ +/** + * 注册 + */ +export type RegisterForm = { + tenantId: string; + username: string; + password: string; + confirmPassword?: string; + code?: string; + uuid?: string; + userType?: string; +}; + +/** + * 登录请求 + */ +export interface LoginData { + tenantId?: string; + username?: string; + password?: string; + rememberMe?: boolean; + socialCode?: string; + socialState?: string; + source?: string; + code?: string; + uuid?: string; + clientId: string; + grantType: string; +} + +/** + * 登录响应 + */ +export interface LoginResult { + access_token: string; +} + +/** + * 验证码返回 + */ +export interface VerifyCodeResult { + captchaEnabled: boolean; + uuid?: string; + img?: string; +} + +/** + * 租户 + */ +export interface TenantVO { + companyName: string; + domain: any; + tenantId: string; +} + +export interface TenantInfo { + tenantEnabled: boolean; + voList: TenantVO[]; +} diff --git a/src/assets/401_images/401.gif b/src/assets/401_images/401.gif new file mode 100644 index 0000000..cd6e0d9 Binary files /dev/null and b/src/assets/401_images/401.gif differ diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/src/assets/404_images/404.png differ diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ diff --git a/src/assets/icons/svg/404.svg b/src/assets/icons/svg/404.svg new file mode 100644 index 0000000..6df5019 --- /dev/null +++ b/src/assets/icons/svg/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/bug.svg b/src/assets/icons/svg/bug.svg new file mode 100644 index 0000000..05a150d --- /dev/null +++ b/src/assets/icons/svg/bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/build.svg b/src/assets/icons/svg/build.svg new file mode 100644 index 0000000..97c4688 --- /dev/null +++ b/src/assets/icons/svg/build.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/button.svg b/src/assets/icons/svg/button.svg new file mode 100644 index 0000000..904fddc --- /dev/null +++ b/src/assets/icons/svg/button.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/cascader.svg b/src/assets/icons/svg/cascader.svg new file mode 100644 index 0000000..e256024 --- /dev/null +++ b/src/assets/icons/svg/cascader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/chart.svg b/src/assets/icons/svg/chart.svg new file mode 100644 index 0000000..27728fb --- /dev/null +++ b/src/assets/icons/svg/chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/checkbox.svg b/src/assets/icons/svg/checkbox.svg new file mode 100644 index 0000000..013fd3a --- /dev/null +++ b/src/assets/icons/svg/checkbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/clipboard.svg b/src/assets/icons/svg/clipboard.svg new file mode 100644 index 0000000..90923ff --- /dev/null +++ b/src/assets/icons/svg/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/code.svg b/src/assets/icons/svg/code.svg new file mode 100644 index 0000000..5f9c5ab --- /dev/null +++ b/src/assets/icons/svg/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/color.svg b/src/assets/icons/svg/color.svg new file mode 100644 index 0000000..44a81aa --- /dev/null +++ b/src/assets/icons/svg/color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/company.svg b/src/assets/icons/svg/company.svg new file mode 100644 index 0000000..fcf1394 --- /dev/null +++ b/src/assets/icons/svg/company.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/component.svg b/src/assets/icons/svg/component.svg new file mode 100644 index 0000000..29c3458 --- /dev/null +++ b/src/assets/icons/svg/component.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/dashboard.svg b/src/assets/icons/svg/dashboard.svg new file mode 100644 index 0000000..5317d37 --- /dev/null +++ b/src/assets/icons/svg/dashboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/date-range.svg b/src/assets/icons/svg/date-range.svg new file mode 100644 index 0000000..fda571e --- /dev/null +++ b/src/assets/icons/svg/date-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/date.svg b/src/assets/icons/svg/date.svg new file mode 100644 index 0000000..52dc73e --- /dev/null +++ b/src/assets/icons/svg/date.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/dict.svg b/src/assets/icons/svg/dict.svg new file mode 100644 index 0000000..4849377 --- /dev/null +++ b/src/assets/icons/svg/dict.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/documentation.svg b/src/assets/icons/svg/documentation.svg new file mode 100644 index 0000000..7043122 --- /dev/null +++ b/src/assets/icons/svg/documentation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/download.svg b/src/assets/icons/svg/download.svg new file mode 100644 index 0000000..c896951 --- /dev/null +++ b/src/assets/icons/svg/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/drag.svg b/src/assets/icons/svg/drag.svg new file mode 100644 index 0000000..4185d3c --- /dev/null +++ b/src/assets/icons/svg/drag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/druid.svg b/src/assets/icons/svg/druid.svg new file mode 100644 index 0000000..a2b4b4e --- /dev/null +++ b/src/assets/icons/svg/druid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/edit.svg b/src/assets/icons/svg/edit.svg new file mode 100644 index 0000000..d26101f --- /dev/null +++ b/src/assets/icons/svg/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/education.svg b/src/assets/icons/svg/education.svg new file mode 100644 index 0000000..7bfb01d --- /dev/null +++ b/src/assets/icons/svg/education.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/email.svg b/src/assets/icons/svg/email.svg new file mode 100644 index 0000000..74d25e2 --- /dev/null +++ b/src/assets/icons/svg/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/example.svg b/src/assets/icons/svg/example.svg new file mode 100644 index 0000000..46f42b5 --- /dev/null +++ b/src/assets/icons/svg/example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/excel.svg b/src/assets/icons/svg/excel.svg new file mode 100644 index 0000000..74d97b8 --- /dev/null +++ b/src/assets/icons/svg/excel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/exit-fullscreen.svg b/src/assets/icons/svg/exit-fullscreen.svg new file mode 100644 index 0000000..485c128 --- /dev/null +++ b/src/assets/icons/svg/exit-fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/eye-open.svg b/src/assets/icons/svg/eye-open.svg new file mode 100644 index 0000000..88dcc98 --- /dev/null +++ b/src/assets/icons/svg/eye-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/eye.svg b/src/assets/icons/svg/eye.svg new file mode 100644 index 0000000..16ed2d8 --- /dev/null +++ b/src/assets/icons/svg/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/form.svg b/src/assets/icons/svg/form.svg new file mode 100644 index 0000000..dcbaa18 --- /dev/null +++ b/src/assets/icons/svg/form.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/fullscreen.svg b/src/assets/icons/svg/fullscreen.svg new file mode 100644 index 0000000..0e86b6f --- /dev/null +++ b/src/assets/icons/svg/fullscreen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/gitee.svg b/src/assets/icons/svg/gitee.svg new file mode 100644 index 0000000..6324608 --- /dev/null +++ b/src/assets/icons/svg/gitee.svg @@ -0,0 +1 @@ + diff --git a/src/assets/icons/svg/github.svg b/src/assets/icons/svg/github.svg new file mode 100644 index 0000000..db0a0d4 --- /dev/null +++ b/src/assets/icons/svg/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/guide.svg b/src/assets/icons/svg/guide.svg new file mode 100644 index 0000000..b271001 --- /dev/null +++ b/src/assets/icons/svg/guide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/icon.svg b/src/assets/icons/svg/icon.svg new file mode 100644 index 0000000..82be8ee --- /dev/null +++ b/src/assets/icons/svg/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/input.svg b/src/assets/icons/svg/input.svg new file mode 100644 index 0000000..ab91381 --- /dev/null +++ b/src/assets/icons/svg/input.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/international.svg b/src/assets/icons/svg/international.svg new file mode 100644 index 0000000..e9b56ee --- /dev/null +++ b/src/assets/icons/svg/international.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/job.svg b/src/assets/icons/svg/job.svg new file mode 100644 index 0000000..2a93a25 --- /dev/null +++ b/src/assets/icons/svg/job.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/language.svg b/src/assets/icons/svg/language.svg new file mode 100644 index 0000000..0082b57 --- /dev/null +++ b/src/assets/icons/svg/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/link.svg b/src/assets/icons/svg/link.svg new file mode 100644 index 0000000..48197ba --- /dev/null +++ b/src/assets/icons/svg/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/list.svg b/src/assets/icons/svg/list.svg new file mode 100644 index 0000000..20259ed --- /dev/null +++ b/src/assets/icons/svg/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/lock.svg b/src/assets/icons/svg/lock.svg new file mode 100644 index 0000000..74fee54 --- /dev/null +++ b/src/assets/icons/svg/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/log.svg b/src/assets/icons/svg/log.svg new file mode 100644 index 0000000..d879d33 --- /dev/null +++ b/src/assets/icons/svg/log.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/logininfor.svg b/src/assets/icons/svg/logininfor.svg new file mode 100644 index 0000000..267f844 --- /dev/null +++ b/src/assets/icons/svg/logininfor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/maxkey.svg b/src/assets/icons/svg/maxkey.svg new file mode 100644 index 0000000..f8f8a7d --- /dev/null +++ b/src/assets/icons/svg/maxkey.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/svg/message.svg b/src/assets/icons/svg/message.svg new file mode 100644 index 0000000..14ca817 --- /dev/null +++ b/src/assets/icons/svg/message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/money.svg b/src/assets/icons/svg/money.svg new file mode 100644 index 0000000..c1580de --- /dev/null +++ b/src/assets/icons/svg/money.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/monitor.svg b/src/assets/icons/svg/monitor.svg new file mode 100644 index 0000000..bc308cb --- /dev/null +++ b/src/assets/icons/svg/monitor.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/nested.svg b/src/assets/icons/svg/nested.svg new file mode 100644 index 0000000..06713a8 --- /dev/null +++ b/src/assets/icons/svg/nested.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/number.svg b/src/assets/icons/svg/number.svg new file mode 100644 index 0000000..ad5ce9a --- /dev/null +++ b/src/assets/icons/svg/number.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/online.svg b/src/assets/icons/svg/online.svg new file mode 100644 index 0000000..330a202 --- /dev/null +++ b/src/assets/icons/svg/online.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/password.svg b/src/assets/icons/svg/password.svg new file mode 100644 index 0000000..6c64def --- /dev/null +++ b/src/assets/icons/svg/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/pdf.svg b/src/assets/icons/svg/pdf.svg new file mode 100644 index 0000000..957aa0c --- /dev/null +++ b/src/assets/icons/svg/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/people.svg b/src/assets/icons/svg/people.svg new file mode 100644 index 0000000..2bd54ae --- /dev/null +++ b/src/assets/icons/svg/people.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/peoples.svg b/src/assets/icons/svg/peoples.svg new file mode 100644 index 0000000..aab852e --- /dev/null +++ b/src/assets/icons/svg/peoples.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/phone.svg b/src/assets/icons/svg/phone.svg new file mode 100644 index 0000000..ab8e8c4 --- /dev/null +++ b/src/assets/icons/svg/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/post.svg b/src/assets/icons/svg/post.svg new file mode 100644 index 0000000..2922c61 --- /dev/null +++ b/src/assets/icons/svg/post.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/qq.svg b/src/assets/icons/svg/qq.svg new file mode 100644 index 0000000..ee13d4e --- /dev/null +++ b/src/assets/icons/svg/qq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/question.svg b/src/assets/icons/svg/question.svg new file mode 100644 index 0000000..cf75bd4 --- /dev/null +++ b/src/assets/icons/svg/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/radio.svg b/src/assets/icons/svg/radio.svg new file mode 100644 index 0000000..0cde345 --- /dev/null +++ b/src/assets/icons/svg/radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/rate.svg b/src/assets/icons/svg/rate.svg new file mode 100644 index 0000000..aa3b14d --- /dev/null +++ b/src/assets/icons/svg/rate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/redis-list.svg b/src/assets/icons/svg/redis-list.svg new file mode 100644 index 0000000..98a15b2 --- /dev/null +++ b/src/assets/icons/svg/redis-list.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/redis.svg b/src/assets/icons/svg/redis.svg new file mode 100644 index 0000000..2f1d62d --- /dev/null +++ b/src/assets/icons/svg/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/row.svg b/src/assets/icons/svg/row.svg new file mode 100644 index 0000000..0780992 --- /dev/null +++ b/src/assets/icons/svg/row.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/search.svg b/src/assets/icons/svg/search.svg new file mode 100644 index 0000000..84233dd --- /dev/null +++ b/src/assets/icons/svg/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/select.svg b/src/assets/icons/svg/select.svg new file mode 100644 index 0000000..d628382 --- /dev/null +++ b/src/assets/icons/svg/select.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/server.svg b/src/assets/icons/svg/server.svg new file mode 100644 index 0000000..eb287e3 --- /dev/null +++ b/src/assets/icons/svg/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/shopping.svg b/src/assets/icons/svg/shopping.svg new file mode 100644 index 0000000..87513e7 --- /dev/null +++ b/src/assets/icons/svg/shopping.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/size.svg b/src/assets/icons/svg/size.svg new file mode 100644 index 0000000..ddb25b8 --- /dev/null +++ b/src/assets/icons/svg/size.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/skill.svg b/src/assets/icons/svg/skill.svg new file mode 100644 index 0000000..a3b7312 --- /dev/null +++ b/src/assets/icons/svg/skill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/slider.svg b/src/assets/icons/svg/slider.svg new file mode 100644 index 0000000..fbe4f39 --- /dev/null +++ b/src/assets/icons/svg/slider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/star.svg b/src/assets/icons/svg/star.svg new file mode 100644 index 0000000..6cf86e6 --- /dev/null +++ b/src/assets/icons/svg/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/swagger.svg b/src/assets/icons/svg/swagger.svg new file mode 100644 index 0000000..05d4e7b --- /dev/null +++ b/src/assets/icons/svg/swagger.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/switch.svg b/src/assets/icons/svg/switch.svg new file mode 100644 index 0000000..0ba61e3 --- /dev/null +++ b/src/assets/icons/svg/switch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/system.svg b/src/assets/icons/svg/system.svg new file mode 100644 index 0000000..5992593 --- /dev/null +++ b/src/assets/icons/svg/system.svg @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tab.svg b/src/assets/icons/svg/tab.svg new file mode 100644 index 0000000..b4b48e4 --- /dev/null +++ b/src/assets/icons/svg/tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/table.svg b/src/assets/icons/svg/table.svg new file mode 100644 index 0000000..0e3dc9d --- /dev/null +++ b/src/assets/icons/svg/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/textarea.svg b/src/assets/icons/svg/textarea.svg new file mode 100644 index 0000000..2709f29 --- /dev/null +++ b/src/assets/icons/svg/textarea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/theme.svg b/src/assets/icons/svg/theme.svg new file mode 100644 index 0000000..5982a2f --- /dev/null +++ b/src/assets/icons/svg/theme.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/time-range.svg b/src/assets/icons/svg/time-range.svg new file mode 100644 index 0000000..13c1202 --- /dev/null +++ b/src/assets/icons/svg/time-range.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/time.svg b/src/assets/icons/svg/time.svg new file mode 100644 index 0000000..b376e32 --- /dev/null +++ b/src/assets/icons/svg/time.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tool.svg b/src/assets/icons/svg/tool.svg new file mode 100644 index 0000000..48e0e35 --- /dev/null +++ b/src/assets/icons/svg/tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tree-table.svg b/src/assets/icons/svg/tree-table.svg new file mode 100644 index 0000000..8aafdb8 --- /dev/null +++ b/src/assets/icons/svg/tree-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/tree.svg b/src/assets/icons/svg/tree.svg new file mode 100644 index 0000000..dd4b7dd --- /dev/null +++ b/src/assets/icons/svg/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/upload.svg b/src/assets/icons/svg/upload.svg new file mode 100644 index 0000000..bae49c0 --- /dev/null +++ b/src/assets/icons/svg/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/user.svg b/src/assets/icons/svg/user.svg new file mode 100644 index 0000000..0ba0716 --- /dev/null +++ b/src/assets/icons/svg/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/validCode.svg b/src/assets/icons/svg/validCode.svg new file mode 100644 index 0000000..cfb1021 --- /dev/null +++ b/src/assets/icons/svg/validCode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/wechat.svg b/src/assets/icons/svg/wechat.svg new file mode 100644 index 0000000..c586e55 --- /dev/null +++ b/src/assets/icons/svg/wechat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/svg/zip.svg b/src/assets/icons/svg/zip.svg new file mode 100644 index 0000000..f806fc4 --- /dev/null +++ b/src/assets/icons/svg/zip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/dark.svg b/src/assets/images/dark.svg new file mode 100644 index 0000000..f646bd7 --- /dev/null +++ b/src/assets/images/dark.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/light.svg b/src/assets/images/light.svg new file mode 100644 index 0000000..ab7cc08 --- /dev/null +++ b/src/assets/images/light.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/images/login-background.jpg b/src/assets/images/login-background.jpg new file mode 100644 index 0000000..fa6408b Binary files /dev/null and b/src/assets/images/login-background.jpg differ diff --git a/src/assets/images/profile.jpg b/src/assets/images/profile.jpg new file mode 100644 index 0000000..f4fde57 Binary files /dev/null and b/src/assets/images/profile.jpg differ diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png new file mode 100644 index 0000000..3f919d8 Binary files /dev/null and b/src/assets/logo/logo.png differ diff --git a/src/assets/styles/btn.scss b/src/assets/styles/btn.scss new file mode 100644 index 0000000..a1ccd01 --- /dev/null +++ b/src/assets/styles/btn.scss @@ -0,0 +1,99 @@ +@import './variables.module.scss'; + +@mixin colorBtn($color) { + background: $color; + + &:hover { + color: $color; + + &:before, + &:after { + background: $color; + } + } +} + +.blue-btn { + @include colorBtn($blue); +} + +.light-blue-btn { + @include colorBtn($light-blue); +} + +.red-btn { + @include colorBtn($red); +} + +.pink-btn { + @include colorBtn($pink); +} + +.green-btn { + @include colorBtn($green); +} + +.tiffany-btn { + @include colorBtn($tiffany); +} + +.yellow-btn { + @include colorBtn($yellow); +} + +.pan-btn { + font-size: 14px; + color: #fff; + padding: 14px 36px; + border-radius: 8px; + border: none; + outline: none; + transition: 600ms ease all; + position: relative; + display: inline-block; + + &:hover { + background: #fff; + + &:before, + &:after { + width: 100%; + transition: 600ms ease all; + } + } + + &:before, + &:after { + content: ''; + position: absolute; + top: 0; + right: 0; + height: 2px; + width: 0; + transition: 400ms ease all; + } + + &::after { + right: inherit; + top: inherit; + left: 0; + bottom: 0; + } +} + +.custom-button { + display: inline-block; + line-height: 1; + white-space: nowrap; + cursor: pointer; + background: #fff; + color: #fff; + -webkit-appearance: none; + text-align: center; + box-sizing: border-box; + outline: 0; + margin: 0; + padding: 10px 15px; + font-size: 14px; + border-radius: 4px; +} diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss new file mode 100644 index 0000000..43c093c --- /dev/null +++ b/src/assets/styles/element-ui.scss @@ -0,0 +1,116 @@ +// cover some element-ui styles + +.el-divider--horizontal { + margin-bottom: 10px; + margin-top: 10px; +} + +.el-breadcrumb__inner, +.el-breadcrumb__inner a { + font-weight: 400 !important; +} + +.el-upload { + input[type='file'] { + display: none !important; + } +} + +.el-upload__input { + display: none; +} + +.cell { + .el-tag { + margin-right: 0px; + } +} + +.small-padding { + .cell { + padding-left: 5px; + padding-right: 5px; + } +} + +.fixed-width { + .el-button--mini { + padding: 7px 10px; + width: 60px; + } +} + +.status-col { + .cell { + padding: 0 10px; + text-align: center; + + .el-tag { + margin-right: 0px; + } + } +} + +/*-------------Dialog-------------**/ +.el-overlay { + overflow: hidden; + + .el-overlay-dialog { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + + .el-dialog { + margin: 0 auto !important; + + .el-dialog__body { + padding: 15px !important; + } + } + } +} + +.el-dialog__body { + max-height: calc(90vh - 111px) !important; + overflow-y: auto; + overflow-x: hidden; +} + +// refine element ui upload +.upload-container { + .el-upload { + width: 100%; + + .el-upload-dragger { + width: 100%; + height: 200px; + } + } +} + +// dropdown +.el-dropdown-menu { + a { + display: block; + } +} + +// fix date-picker ui bug in filter-item +.el-range-editor.el-input__inner { + display: inline-flex !important; +} + +// to fix el-date-picker css style +.el-range-separator { + box-sizing: content-box; +} + +.el-menu--collapse > div > .el-submenu > .el-submenu__title .el-submenu__icon-arrow { + display: none; +} + +.el-dropdown .el-dropdown-link { + color: var(--el-color-primary) !important; +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss new file mode 100644 index 0000000..48865fb --- /dev/null +++ b/src/assets/styles/index.scss @@ -0,0 +1,208 @@ +@import './variables.module.scss'; +@import './mixin.scss'; +@import './transition.scss'; +@import './element-ui.scss'; +@import './sidebar.scss'; +@import './btn.scss'; +@import './ruoyi.scss'; +@import 'animate.css'; +@import 'element-plus/dist/index.css'; + +body { + height: 100%; + margin: 0; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; +} + +label { + font-weight: 700; +} + +html { + height: 100%; + box-sizing: border-box; +} + +html.dark .svg-icon, +html.dark svg { + fill: var(--el-text-color-regular); +} + +#app { + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +.no-padding { + padding: 0px !important; +} + +.padding-content { + padding: 4px 0; +} + +a:focus, +a:active { + outline: none; +} + +a, +a:focus, +a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; +} + +div:focus { + outline: none; +} + +.fr { + float: right; +} + +.fl { + float: left; +} + +.pr-5 { + padding-right: 5px; +} + +.pl-5 { + padding-left: 5px; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.inlineBlock { + display: block; +} + +.clearfix { + &:after { + visibility: hidden; + display: block; + font-size: 0; + content: ' '; + clear: both; + height: 0; + } +} + +aside { + background: #eef1f6; + padding: 8px 24px; + margin-bottom: 20px; + border-radius: 2px; + display: block; + line-height: 32px; + font-size: 16px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + color: #2c3e50; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + a { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } + } +} + +//main-container全局样式 +.app-container { + padding: 20px; +} + +// search面板样式 +.panel, +.search { + margin-bottom: 0.75rem; + border-radius: 0.25rem; + border: 1px solid var(--el-border-color-light); + background-color: var(--el-bg-color-overlay); + padding: 0.75rem; + transition: all ease 0.3s; + + &:hover { + box-shadow: 0 2px 12px #0000001a; + transition: all ease 0.3s; + } +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +.pagination-container { + margin-top: 30px; +} + +.text-center { + text-align: center; +} + +.sub-navbar { + height: 50px; + line-height: 50px; + position: relative; + width: 100%; + text-align: right; + padding-right: 20px; + transition: 600ms ease position; + background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); + + .subtitle { + font-size: 20px; + color: #fff; + } + + &.draft { + background: #d0d0d0; + } + + &.deleted { + background: #d0d0d0; + } +} + +.link-type, +.link-type:focus { + color: #337ab7; + cursor: pointer; + + &:hover { + color: rgb(32, 160, 255); + } +} + +.filter-container { + padding-bottom: 10px; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: 10px; + } +} diff --git a/src/assets/styles/mixin.scss b/src/assets/styles/mixin.scss new file mode 100644 index 0000000..5250e71 --- /dev/null +++ b/src/assets/styles/mixin.scss @@ -0,0 +1,60 @@ +@mixin clearfix { + &:after { + content: ''; + display: table; + clear: both; + } +} + +@mixin scrollBar { + &::-webkit-scrollbar-track-piece { + background: #d3dce6; + } + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #99a9bf; + border-radius: 20px; + } +} + +@mixin relative { + position: relative; + width: 100%; + height: 100%; +} + +@mixin pct($pct) { + width: #{$pct}; + position: relative; + margin: 0 auto; +} + +@mixin triangle($width, $height, $color, $direction) { + $width: $width/2; + $color-border-style: $height solid $color; + $transparent-border-style: $width solid transparent; + height: 0; + width: 0; + + @if $direction==up { + border-bottom: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } @else if $direction==right { + border-left: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } @else if $direction==down { + border-top: $color-border-style; + border-left: $transparent-border-style; + border-right: $transparent-border-style; + } @else if $direction==left { + border-right: $color-border-style; + border-top: $transparent-border-style; + border-bottom: $transparent-border-style; + } +} diff --git a/src/assets/styles/ruoyi.scss b/src/assets/styles/ruoyi.scss new file mode 100644 index 0000000..d2ecb24 --- /dev/null +++ b/src/assets/styles/ruoyi.scss @@ -0,0 +1,290 @@ +/** + * 通用css样式布局处理 + * Copyright (c) 2019 ruoyi + */ + +/** 基础通用 **/ +.pt5 { + padding-top: 5px; +} +.pr5 { + padding-right: 5px; +} +.pb5 { + padding-bottom: 5px; +} +.mt5 { + margin-top: 5px; +} +.mr5 { + margin-right: 5px; +} +.mb5 { + margin-bottom: 5px; +} +.mb8 { + margin-bottom: 8px; +} +.ml5 { + margin-left: 5px; +} +.mt10 { + margin-top: 10px; +} +.mr10 { + margin-right: 10px; +} +.mb10 { + margin-bottom: 10px; +} +.ml10 { + margin-left: 10px; +} +.mt20 { + margin-top: 20px; +} +.mr20 { + margin-right: 20px; +} +.mb20 { + margin-bottom: 20px; +} +.ml20 { + margin-left: 20px; +} + +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.el-form .el-form-item__label { + font-weight: 700; +} +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog.scrollbar .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + .el-table__header-wrapper, + .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: $table-header-bg !important; + color: $table-header-text-color; + height: 40px !important; + font-size: 13px; + } + } + .el-table__body-wrapper { + .el-button [class*='el-icon-'] + span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size: 15px; + color: #6379bb; + border-bottom: 1px solid #ddd; + margin: 8px 10px 25px 10px; + padding-bottom: 5px; +} + +/** 表格布局 **/ +.pagination-container { + // position: relative; + height: 25px; + margin-bottom: 10px; + margin-top: 15px; + padding: 10px 20px !important; +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid #e5e6e7; + background: #ffffff none; + border-radius: 4px; + width: 100%; +} + +.pagination-container .el-pagination { + //right: 0; + //position: absolute; +} + +@media (max-width: 768px) { + .pagination-container .el-pagination > .el-pagination__jump { + display: none !important; + } + .pagination-container .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-table .fixed-width .el-button--small { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link { + cursor: pointer; + color: #409eff; + margin-left: 10px; +} + +.el-table .el-dropdown, +.el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content > .el-checkbox { + margin-right: 8px; +} + +.list-group-striped > .list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 7px !important; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px !important; +} + +.card-box { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20b2aa; + border-color: #20b2aa; + color: #ffffff; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48d1cc; + border-color: #48d1cc; + color: #ffffff; +} + +.el-button--cyan { + background-color: #20b2aa; + border-color: #20b2aa; + color: #ffffff; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: absolute; + top: 50%; + transform: translate(50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost { + opacity: 0.8; + color: #fff !important; + background: #42b983 !important; +} + +/* 表格右侧工具栏样式 */ +.top-right-btn { + margin-left: auto; +} diff --git a/src/assets/styles/sidebar.scss b/src/assets/styles/sidebar.scss new file mode 100644 index 0000000..d47a2bd --- /dev/null +++ b/src/assets/styles/sidebar.scss @@ -0,0 +1,230 @@ +#app { + .main-container { + height: 100%; + transition: margin-left 0.28s; + margin-left: $base-sidebar-width; + position: relative; + } + + .sidebarHide { + margin-left: 0 !important; + } + + .sidebar-container { + -webkit-transition: width 0.28s; + transition: width 0.28s; + width: $base-sidebar-width !important; + background-color: $base-menu-background; + height: 100%; + position: fixed; + font-size: 0; + top: 0; + bottom: 0; + left: 0; + z-index: 1001; + overflow: hidden; + -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); + box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); + + // reset element-ui css + .horizontal-collapse-transition { + transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; + } + + .scrollbar-wrapper { + overflow-x: hidden !important; + } + + .el-scrollbar__bar.is-vertical { + right: 0; + } + + .el-scrollbar { + height: 100%; + } + + &.has-logo { + .el-scrollbar { + height: calc(100% - 50px); + } + } + + .is-horizontal { + display: none; + } + + a { + display: inline-block; + width: 100%; + overflow: hidden; + } + + .svg-icon { + margin-right: 16px; + } + + .el-menu { + border: none; + height: 100%; + width: 100% !important; + } + + .el-menu-item, + .menu-title { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } + + .el-menu-item .el-menu-tooltip__trigger { + display: inline-block !important; + } + + // menu hover + .theme-dark .sub-menu-title-noDropdown, + .theme-dark .el-sub-menu__title { + &:hover { + background-color: $base-sub-menu-title-hover !important; + } + } + .sub-menu-title-noDropdown, + .el-sub-menu__title { + &:hover { + background-color: rgba(0, 0, 0, 0.05) !important; + } + } + + & .theme-dark .is-active > .el-sub-menu__title { + color: $base-menu-color-active !important; + } + + & .nest-menu .el-sub-menu > .el-sub-menu__title, + & .el-sub-menu .el-menu-item { + min-width: $base-sidebar-width !important; + &:hover { + background-color: rgba(0, 0, 0, 0.1) !important; + } + } + + + & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title, + & .theme-dark .el-sub-menu .el-menu-item { + background-color: $base-sub-menu-background !important; + + &:hover { + background-color: $base-sub-menu-hover !important; + } + } + + & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title, + & .theme-dark .el-menu-item { + &:hover { + // you can use $sub-menuHover + background-color: $base-menu-hover !important; + } + } + & .nest-menu .el-sub-menu > .el-sub-menu__title, + & .el-menu-item { + &:hover { + // you can use $sub-menuHover + background-color: rgba(0, 0, 0, 0.04) !important; + } + } + } + + .hideSidebar { + .sidebar-container { + width: 54px !important; + } + + .main-container { + margin-left: 54px; + } + + .sub-menu-title-noDropdown { + padding: 0 !important; + position: relative; + + .el-tooltip { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-sub-menu { + overflow: hidden; + + & > .el-sub-menu__title { + padding: 0 !important; + + .svg-icon { + margin-left: 20px; + } + } + } + + .el-menu--collapse { + .el-sub-menu { + & > .el-sub-menu__title { + & > span { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + & > i { + height: 0; + width: 0; + overflow: hidden; + visibility: hidden; + display: inline-block; + } + } + } + } + } + + .el-menu--collapse .el-menu .el-sub-menu { + min-width: $base-sidebar-width !important; + } + + // mobile responsive + .mobile { + .main-container { + margin-left: 0px; + } + + .sidebar-container { + transition: transform 0.28s; + width: $base-sidebar-width !important; + } + + &.hideSidebar { + .sidebar-container { + pointer-events: none; + transition-duration: 0.3s; + transform: translate3d(-$base-sidebar-width, 0, 0); + } + } + } + + .withoutAnimation { + .main-container, + .sidebar-container { + transition: none; + } + } +} + +// when menu collapsed +.el-menu--vertical { + & > .el-menu { + .svg-icon { + margin-right: 16px; + } + } +} diff --git a/src/assets/styles/transition.scss b/src/assets/styles/transition.scss new file mode 100644 index 0000000..468ad3c --- /dev/null +++ b/src/assets/styles/transition.scss @@ -0,0 +1,49 @@ +// global transition css + +/* fade */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.28s; +} + +.fade-enter, +.fade-leave-active { + opacity: 0; +} + +/* fade-transform */ +.fade-transform--move, +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all 0.5s; +} + +.fade-transform-enter { + opacity: 0; + transform: translateX(-30px); +} + +.fade-transform-leave-to { + opacity: 0; + transform: translateX(30px); +} + +/* breadcrumb transition */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all 0.5s; +} + +.breadcrumb-enter, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(20px); +} + +.breadcrumb-move { + transition: all 0.5s; +} + +.breadcrumb-leave-active { + position: absolute; +} diff --git a/src/assets/styles/variables.module.scss b/src/assets/styles/variables.module.scss new file mode 100644 index 0000000..d07d3d4 --- /dev/null +++ b/src/assets/styles/variables.module.scss @@ -0,0 +1,93 @@ +// 全局SCSS变量 +:root { + --menuBg: #304156; + --menuColor: #bfcbd9; + --menuActiveText: #f4f4f5; + --menuHover: #263445; + + --subMenuBg: #1f2d3d; + --subMenuActiveText: #f4f4f5; + --subMenuHover: #001528; + --subMenuTitleHover: #293444; + + --fixedHeaderBg: #ffffff; + --tableHeaderBg: #f8f8f9; + --tableHeaderTextColor: #515a6e; +} +html.dark { + --menuBg: #1d1e1f; + --menuColor: #bfcbd9; + --menuActiveText: #f4f4f5; + --menuHover: #171819; + + --subMenuBg: #1d1e1f; + --subMenuActiveText: #1d1e1f; + --subMenuHover: #171819; + --subMenuTitleHover: #171819; + + --fixedHeaderBg: #171819; + --tableHeaderBg: var(--el-bg-color); + --tableHeaderTextColor: var(--el-text-color); + + // 覆盖ele 高亮当前行的标准暗色 + .el-tree-node__content { + --el-color-primary-light-9: #262727; + } +} + +// base color +$blue: #324157; +$light-blue: #3a71a8; +$red: #c03639; +$pink: #e65d6e; +$green: #30b08f; +$tiffany: #4ab7bd; +$yellow: #fec171; +$panGreen: #30b08f; + +// 默认菜单主题风格 +$base-menu-color: var(--menuColor); +$base-menu-hover: var(--menuHover); +$base-menu-color-active: var(--menuActiveText); +$base-menu-background: var(--menuBg); +$base-logo-title-color: #ffffff; + +$base-menu-light-color: rgba(0, 0, 0, 0.7); +$base-menu-light-background: #ffffff; +$base-logo-light-title-color: #001529; + +$base-sub-menu-background: var(--subMenuBg); +$base-sub-menu-hover: var(--subMenuHover); +$base-sub-menu-title-hover: var(--subMenuTitleHover); +// 表单头背景色和标题颜色 +$fixed-header-bg: var(--fixedHeaderBg); +$table-header-bg: var(--tableHeaderBg); +$table-header-text-color: var(--tableHeaderTextColor); + +$--color-primary: #409eff; +$--color-success: #67c23a; +$--color-warning: #e6a23c; +$--color-danger: #f56c6c; +$--color-info: #909399; + +$base-sidebar-width: 200px; + +// the :export directive is the magic sauce for webpack +// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass +:export { + menuColor: $base-menu-color; + menuLightColor: $base-menu-light-color; + menuColorActive: $base-menu-color-active; + menuBackground: $base-menu-background; + menuLightBackground: $base-menu-light-background; + subMenuBackground: $base-sub-menu-background; + subMenuHover: $base-sub-menu-hover; + sideBarWidth: $base-sidebar-width; + logoTitleColor: $base-logo-title-color; + logoLightTitleColor: $base-logo-light-title-color; + primaryColor: $--color-primary; + successColor: $--color-success; + dangerColor: $--color-danger; + infoColor: $--color-info; + warningColor: $--color-warning; +} diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue new file mode 100644 index 0000000..b96829a --- /dev/null +++ b/src/components/Breadcrumb/index.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/BuildCode/index.vue b/src/components/BuildCode/index.vue new file mode 100644 index 0000000..b23665c --- /dev/null +++ b/src/components/BuildCode/index.vue @@ -0,0 +1,64 @@ + + + + + + diff --git a/src/components/BuildCode/render.vue b/src/components/BuildCode/render.vue new file mode 100644 index 0000000..62f3d86 --- /dev/null +++ b/src/components/BuildCode/render.vue @@ -0,0 +1,62 @@ + + + + diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue new file mode 100644 index 0000000..45b288b --- /dev/null +++ b/src/components/DictTag/index.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue new file mode 100644 index 0000000..b5299b4 --- /dev/null +++ b/src/components/Editor/index.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/src/components/FileUpload/index.vue b/src/components/FileUpload/index.vue new file mode 100644 index 0000000..be99ac0 --- /dev/null +++ b/src/components/FileUpload/index.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue new file mode 100644 index 0000000..489ac43 --- /dev/null +++ b/src/components/Hamburger/index.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/components/HeaderSearch/index.vue b/src/components/HeaderSearch/index.vue new file mode 100644 index 0000000..b922c6c --- /dev/null +++ b/src/components/HeaderSearch/index.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue new file mode 100644 index 0000000..1cd0023 --- /dev/null +++ b/src/components/IconSelect/index.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/IconSelect/requireIcons.ts b/src/components/IconSelect/requireIcons.ts new file mode 100644 index 0000000..9f06d69 --- /dev/null +++ b/src/components/IconSelect/requireIcons.ts @@ -0,0 +1,7 @@ +const icons: string[] = []; +const modules = import.meta.glob('./../../assets/icons/svg/*.svg'); +for (const path in modules) { + const p = path.split('assets/icons/svg/')[1].split('.svg')[0]; + icons.push(p); +} +export default icons; diff --git a/src/components/ImagePreview/index.vue b/src/components/ImagePreview/index.vue new file mode 100644 index 0000000..723ccdb --- /dev/null +++ b/src/components/ImagePreview/index.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue new file mode 100644 index 0000000..d8ed32b --- /dev/null +++ b/src/components/ImageUpload/index.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/src/components/LangSelect/index.vue b/src/components/LangSelect/index.vue new file mode 100644 index 0000000..a30a2f6 --- /dev/null +++ b/src/components/LangSelect/index.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue new file mode 100644 index 0000000..df73ceb --- /dev/null +++ b/src/components/Pagination/index.vue @@ -0,0 +1,88 @@ + + + + + + + diff --git a/src/components/ParentView/index.vue b/src/components/ParentView/index.vue new file mode 100644 index 0000000..98240ae --- /dev/null +++ b/src/components/ParentView/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/RightToolbar/index.vue b/src/components/RightToolbar/index.vue new file mode 100644 index 0000000..6be382f --- /dev/null +++ b/src/components/RightToolbar/index.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/src/components/RuoYiDoc/index.vue b/src/components/RuoYiDoc/index.vue new file mode 100644 index 0000000..6c7ac0d --- /dev/null +++ b/src/components/RuoYiDoc/index.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/RuoYiGit/index.vue b/src/components/RuoYiGit/index.vue new file mode 100644 index 0000000..82393eb --- /dev/null +++ b/src/components/RuoYiGit/index.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/Screenfull/index.vue b/src/components/Screenfull/index.vue new file mode 100644 index 0000000..ce0b373 --- /dev/null +++ b/src/components/Screenfull/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/SizeSelect/index.vue b/src/components/SizeSelect/index.vue new file mode 100644 index 0000000..058ff71 --- /dev/null +++ b/src/components/SizeSelect/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue new file mode 100644 index 0000000..3a7540f --- /dev/null +++ b/src/components/SvgIcon/index.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/components/TopNav/index.vue b/src/components/TopNav/index.vue new file mode 100644 index 0000000..227fccc --- /dev/null +++ b/src/components/TopNav/index.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/components/TreeSelect/index.vue b/src/components/TreeSelect/index.vue new file mode 100644 index 0000000..3076804 --- /dev/null +++ b/src/components/TreeSelect/index.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/src/components/iFrame/index.vue b/src/components/iFrame/index.vue new file mode 100644 index 0000000..6679f08 --- /dev/null +++ b/src/components/iFrame/index.vue @@ -0,0 +1,26 @@ + + + diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue new file mode 100644 index 0000000..7818fd4 --- /dev/null +++ b/src/layout/components/Navbar.vue @@ -0,0 +1,296 @@ + + + + + diff --git a/src/layout/components/Settings/index.vue b/src/layout/components/Settings/index.vue new file mode 100644 index 0000000..d5522ae --- /dev/null +++ b/src/layout/components/Settings/index.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue new file mode 100644 index 0000000..4c6e8b3 --- /dev/null +++ b/src/layout/components/Sidebar/Link.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue new file mode 100644 index 0000000..a06158a --- /dev/null +++ b/src/layout/components/Sidebar/Logo.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue new file mode 100644 index 0000000..4459cdf --- /dev/null +++ b/src/layout/components/Sidebar/SidebarItem.vue @@ -0,0 +1,108 @@ + + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..fc290b1 --- /dev/null +++ b/src/layout/components/Sidebar/index.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/layout/components/SocialCallback/index.vue b/src/layout/components/SocialCallback/index.vue new file mode 100644 index 0000000..e3a50e0 --- /dev/null +++ b/src/layout/components/SocialCallback/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/layout/components/TagsView/ScrollPane.vue b/src/layout/components/TagsView/ScrollPane.vue new file mode 100644 index 0000000..b50c628 --- /dev/null +++ b/src/layout/components/TagsView/ScrollPane.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue new file mode 100644 index 0000000..1df486e --- /dev/null +++ b/src/layout/components/TagsView/index.vue @@ -0,0 +1,335 @@ + + + + + + + diff --git a/src/layout/components/TopBar/search.vue b/src/layout/components/TopBar/search.vue new file mode 100644 index 0000000..9dee360 --- /dev/null +++ b/src/layout/components/TopBar/search.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/src/layout/components/index.ts b/src/layout/components/index.ts new file mode 100644 index 0000000..47c83e1 --- /dev/null +++ b/src/layout/components/index.ts @@ -0,0 +1,4 @@ +export { default as AppMain } from './AppMain.vue'; +export { default as Navbar } from './Navbar.vue'; +export { default as Settings } from './Settings/index.vue'; +export { default as TagsView } from './TagsView/index.vue'; diff --git a/src/layout/components/notice/index.vue b/src/layout/components/notice/index.vue new file mode 100644 index 0000000..ef4a6a9 --- /dev/null +++ b/src/layout/components/notice/index.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..8239ed7 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..0ad939a --- /dev/null +++ b/src/main.ts @@ -0,0 +1,57 @@ +import { createApp } from 'vue'; +// global css +import 'uno.css'; +import '@/assets/styles/index.scss'; +import 'element-plus/theme-chalk/dark/css-vars.css'; + +// App、router、store +import App from './App.vue'; +import store from './store'; +import router from './router'; + +// 自定义指令 +import directive from './directive'; + +// 注册插件 +import plugins from './plugins/index'; // plugins +import { download } from '@/utils/request'; + +// 预设动画 +import animate from './animate'; + +// svg图标 +import 'virtual:svg-icons-register'; +import ElementIcons from '@/plugins/svgicon'; + +// permission control +import './permission'; + +import { useDict } from '@/utils/dict'; +import { getConfigKey, updateConfigByKey } from '@/api/system/config'; +import { parseTime, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'; + +// 国际化 +import i18n from '@/lang/index'; + +const app = createApp(App); +// 全局方法挂载 +app.config.globalProperties.useDict = useDict; +app.config.globalProperties.getConfigKey = getConfigKey; +app.config.globalProperties.updateConfigByKey = updateConfigByKey; +app.config.globalProperties.download = download; +app.config.globalProperties.parseTime = parseTime; +app.config.globalProperties.handleTree = handleTree; +app.config.globalProperties.addDateRange = addDateRange; +app.config.globalProperties.selectDictLabel = selectDictLabel; +app.config.globalProperties.selectDictLabels = selectDictLabels; +app.config.globalProperties.animate = animate; + +app.use(ElementIcons); +app.use(router); +app.use(store); +app.use(i18n); +app.use(plugins); +// 自定义指令 +directive(app); + +app.mount('#app'); diff --git a/src/permission.ts b/src/permission.ts new file mode 100644 index 0000000..c819ff4 --- /dev/null +++ b/src/permission.ts @@ -0,0 +1,63 @@ +import { to as tos } from 'await-to-js'; +import router from './router'; +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; +import { getToken } from '@/utils/auth'; +import { isHttp } from '@/utils/validate'; +import { isRelogin } from '@/utils/request'; +import useUserStore from '@/store/modules/user'; +import useSettingsStore from '@/store/modules/settings'; +import usePermissionStore from '@/store/modules/permission'; + +NProgress.configure({ showSpinner: false }); +const whiteList = ['/login', '/register', '/social-callback']; + +router.beforeEach(async (to, from, next) => { + NProgress.start(); + if (getToken()) { + to.meta.title && useSettingsStore().setTitle(to.meta.title as string); + /* has token*/ + if (to.path === '/login') { + next({ path: '/' }); + NProgress.done(); + } else if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + if (useUserStore().roles.length === 0) { + isRelogin.show = true; + // 判断当前用户是否已拉取完user_info信息 + const [err] = await tos(useUserStore().getInfo()); + if (err) { + await useUserStore().logout(); + ElMessage.error(err); + next({ path: '/' }); + } else { + isRelogin.show = false; + const accessRoutes = await usePermissionStore().generateRoutes(); + // 根据roles权限生成可访问的路由表 + accessRoutes.forEach((route) => { + if (!isHttp(route.path)) { + router.addRoute(route); // 动态添加可访问路由表 + } + }); + next({ ...to, replace: true }); // hack方法 确保addRoutes已完成 + } + } else { + next(); + } + } + } else { + // 没有token + if (whiteList.indexOf(to.path) !== -1) { + // 在免登录白名单,直接进入 + next(); + } else { + next(`/login?redirect=${to.fullPath}`); // 否则全部重定向到登录页 + NProgress.done(); + } + } +}); + +router.afterEach(() => { + NProgress.done(); +}); diff --git a/src/plugins/auth.ts b/src/plugins/auth.ts new file mode 100644 index 0000000..74c91ee --- /dev/null +++ b/src/plugins/auth.ts @@ -0,0 +1,60 @@ +import useUserStore from '@/store/modules/user'; + +const authPermission = (permission: string): boolean => { + const all_permission = '*:*:*'; + const permissions: string[] = useUserStore().permissions; + if (permission && permission.length > 0) { + return permissions.some((v) => { + return all_permission === v || v === permission; + }); + } else { + return false; + } +}; + +const authRole = (role: string): boolean => { + const super_admin = 'admin'; + const roles = useUserStore().roles; + if (role && role.length > 0) { + return roles.some((v) => { + return super_admin === v || v === role; + }); + } else { + return false; + } +}; + +export default { + // 验证用户是否具备某权限 + hasPermi(permission: string): boolean { + return authPermission(permission); + }, + // 验证用户是否含有指定权限,只需包含其中一个 + hasPermiOr(permissions: string[]): boolean { + return permissions.some((item) => { + return authPermission(item); + }); + }, + // 验证用户是否含有指定权限,必须全部拥有 + hasPermiAnd(permissions: string[]): boolean { + return permissions.every((item) => { + return authPermission(item); + }); + }, + // 验证用户是否具备某角色 + hasRole(role: string): boolean { + return authRole(role); + }, + // 验证用户是否含有指定角色,只需包含其中一个 + hasRoleOr(roles: string[]): boolean { + return roles.some((item) => { + return authRole(item); + }); + }, + // 验证用户是否含有指定角色,必须全部拥有 + hasRoleAnd(roles: string[]): boolean { + return roles.every((item) => { + return authRole(item); + }); + } +}; diff --git a/src/plugins/cache.ts b/src/plugins/cache.ts new file mode 100644 index 0000000..cf43680 --- /dev/null +++ b/src/plugins/cache.ts @@ -0,0 +1,77 @@ +const sessionCache = { + set(key: string, value: any) { + if (!sessionStorage) { + return; + } + if (key != null && value != null) { + sessionStorage.setItem(key, value); + } + }, + get(key: string) { + if (!sessionStorage) { + return null; + } + if (key == null) { + return null; + } + return sessionStorage.getItem(key); + }, + setJSON(key: string, jsonValue: any) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)); + } + }, + getJSON(key: string) { + const value = this.get(key); + if (value != null) { + return JSON.parse(value); + } + }, + remove(key: string) { + sessionStorage.removeItem(key); + } +}; +const localCache = { + set(key: string, value: any) { + if (!localStorage) { + return; + } + if (key != null && value != null) { + localStorage.setItem(key, value); + } + }, + get(key: string) { + if (!localStorage) { + return null; + } + if (key == null) { + return null; + } + return localStorage.getItem(key); + }, + setJSON(key: string, jsonValue: any) { + if (jsonValue != null) { + this.set(key, JSON.stringify(jsonValue)); + } + }, + getJSON(key: string) { + const value = this.get(key); + if (value != null) { + return JSON.parse(value); + } + }, + remove(key: string) { + localStorage.removeItem(key); + } +}; + +export default { + /** + * 会话级缓存 + */ + session: sessionCache, + /** + * 本地缓存 + */ + local: localCache +}; diff --git a/src/plugins/download.ts b/src/plugins/download.ts new file mode 100644 index 0000000..ef66b3a --- /dev/null +++ b/src/plugins/download.ts @@ -0,0 +1,65 @@ +import axios from 'axios'; +import FileSaver from 'file-saver'; +import errorCode from '@/utils/errorCode'; +import { blobValidate } from '@/utils/ruoyi'; +import { LoadingInstance } from 'element-plus/es/components/loading/src/loading'; +import { globalHeaders } from '@/utils/request'; + +const baseURL = import.meta.env.VITE_APP_BASE_API; +let downloadLoadingInstance: LoadingInstance; +export default { + async oss(ossId: string | number) { + const url = baseURL + '/resource/oss/download/' + ossId; + downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' }); + try { + const res = await axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: globalHeaders() + }); + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/octet-stream' }); + FileSaver.saveAs(blob, decodeURIComponent(res.headers['download-filename'] as string)); + } else { + this.printErrMsg(res.data); + } + downloadLoadingInstance.close(); + } catch (r) { + console.error(r); + ElMessage.error('下载文件出现错误,请联系管理员!'); + downloadLoadingInstance.close(); + } + }, + async zip(url: string, name: string) { + url = baseURL + url; + downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' }); + try { + const res = await axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: globalHeaders() + }); + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/zip' }); + FileSaver.saveAs(blob, name); + } else { + this.printErrMsg(res.data); + } + downloadLoadingInstance.close(); + } catch (r) { + console.error(r); + ElMessage.error('下载文件出现错误,请联系管理员!'); + downloadLoadingInstance.close(); + } + }, + async printErrMsg(data: any) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']; + ElMessage.error(errMsg); + } +}; diff --git a/src/plugins/index.ts b/src/plugins/index.ts new file mode 100644 index 0000000..09d550a --- /dev/null +++ b/src/plugins/index.ts @@ -0,0 +1,24 @@ +import modal from './modal'; +import tab from './tab'; +import download from './download'; +import cache from './cache'; +import auth from './auth'; + +import { App } from 'vue'; + +export default function installPlugin(app: App) { + // 页签操作 + app.config.globalProperties.$tab = tab; + + // 模态框对象 + app.config.globalProperties.$modal = modal; + + // 缓存对象 + app.config.globalProperties.$cache = cache; + + // 下载文件 + app.config.globalProperties.$download = download; + + // 认证对象 + app.config.globalProperties.$auth = auth; +} diff --git a/src/plugins/modal.ts b/src/plugins/modal.ts new file mode 100644 index 0000000..a8b0548 --- /dev/null +++ b/src/plugins/modal.ts @@ -0,0 +1,81 @@ +import { MessageBoxData } from 'element-plus'; +import { LoadingInstance } from 'element-plus/es/components/loading/src/loading'; +let loadingInstance: LoadingInstance; +export default { + // 消息提示 + msg(content: any) { + ElMessage.info(content); + }, + // 错误消息 + msgError(content: any) { + ElMessage.error(content); + }, + // 成功消息 + msgSuccess(content: any) { + ElMessage.success(content); + }, + // 警告消息 + msgWarning(content: any) { + ElMessage.warning(content); + }, + // 弹出提示 + alert(content: any) { + ElMessageBox.alert(content, '系统提示'); + }, + // 错误提示 + alertError(content: any) { + ElMessageBox.alert(content, '系统提示', { type: 'error' }); + }, + // 成功提示 + alertSuccess(content: any) { + ElMessageBox.alert(content, '系统提示', { type: 'success' }); + }, + // 警告提示 + alertWarning(content: any) { + ElMessageBox.alert(content, '系统提示', { type: 'warning' }); + }, + // 通知提示 + notify(content: any) { + ElNotification.info(content); + }, + // 错误通知 + notifyError(content: any) { + ElNotification.error(content); + }, + // 成功通知 + notifySuccess(content: any) { + ElNotification.success(content); + }, + // 警告通知 + notifyWarning(content: any) { + ElNotification.warning(content); + }, + // 确认窗体 + confirm(content: any): Promise { + return ElMessageBox.confirm(content, '系统提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }); + }, + // 提交内容 + prompt(content: any) { + return ElMessageBox.prompt(content, '系统提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }); + }, + // 打开遮罩层 + loading(content: string) { + loadingInstance = ElLoading.service({ + lock: true, + text: content, + background: 'rgba(0, 0, 0, 0.7)' + }); + }, + // 关闭遮罩层 + closeLoading() { + loadingInstance.close(); + } +}; diff --git a/src/plugins/svgicon.ts b/src/plugins/svgicon.ts new file mode 100644 index 0000000..8c68609 --- /dev/null +++ b/src/plugins/svgicon.ts @@ -0,0 +1,10 @@ +import * as ElementPlusIconsVue from '@element-plus/icons-vue'; +import { App } from 'vue'; + +export default { + install: (app: App) => { + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component); + } + } +}; diff --git a/src/plugins/tab.ts b/src/plugins/tab.ts new file mode 100644 index 0000000..97442ff --- /dev/null +++ b/src/plugins/tab.ts @@ -0,0 +1,86 @@ +import { useTagsViewStore } from '@/store/modules/tagsView'; +import router from '@/router'; +import { TagView, RouteLocationRaw } from 'vue-router'; + +export default { + /** + * 刷新当前tab页签 + * @param obj 标签对象 + */ + async refreshPage(obj?: TagView): Promise { + const { path, query, matched } = router.currentRoute.value; + if (obj === undefined) { + matched.forEach((m) => { + if (m.components && m.components.default && m.components.default.name) { + if (!['Layout', 'ParentView'].includes(m.components.default.name)) { + obj = { name: m.components.default.name, path: path, query: query }; + } + } + }); + } + let query1: undefined | {} = {}; + let path1: undefined | string = ''; + if (obj) { + query1 = obj.query; + path1 = obj.path; + } + await useTagsViewStore().delCachedView(obj); + await router.replace({ + path: '/redirect' + path1, + query: query1 + }); + }, + // 关闭当前tab页签,打开新页签 + closeOpenPage(obj: RouteLocationRaw): void { + useTagsViewStore().delView(router.currentRoute.value); + if (obj !== undefined) { + router.push(obj); + } + }, + // 关闭指定tab页签 + async closePage(obj?: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] } | any> { + if (obj === undefined) { + // prettier-ignore + const { visitedViews } = await useTagsViewStore().delView(router.currentRoute.value) as any + const latestView = visitedViews.slice(-1)[0]; + if (latestView) { + return router.push(latestView.fullPath); + } + return router.push('/'); + } + return useTagsViewStore().delView(obj); + }, + // 关闭所有tab页签 + closeAllPage() { + return useTagsViewStore().delAllViews(); + }, + // 关闭左侧tab页签 + closeLeftPage(obj?: TagView) { + return useTagsViewStore().delLeftTags(obj || router.currentRoute.value); + }, + // 关闭右侧tab页签 + closeRightPage(obj?: TagView) { + return useTagsViewStore().delRightTags(obj || router.currentRoute.value); + }, + // 关闭其他tab页签 + closeOtherPage(obj?: TagView) { + return useTagsViewStore().delOthersViews(obj || router.currentRoute.value); + }, + /** + * 打开tab页签 + * @param url 路由地址 + * @param title 标题 + * @param query 参数 + */ + openPage(url: string, title?: string, query?: any) { + const obj = { path: url, query: { ...query, title } }; + return router.push(obj); + }, + /** + * 修改tab页签 + * @param obj 标签对象 + */ + updatePage(obj: TagView) { + return useTagsViewStore().updateVisitedView(obj); + } +}; diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..271385c --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,184 @@ +import { createWebHistory, createRouter, RouteOption } from 'vue-router'; +/* Layout */ +import Layout from '@/layout/index.vue'; + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes: RouteOption[] = [ + { + path: '/redirect', + component: Layout, + hidden: true, + children: [ + { + path: '/redirect/:path(.*)', + component: () => import('@/views/redirect/index.vue') + } + ] + }, + { + path: '/social-callback', + hidden: true, + component: () => import('@/layout/components/SocialCallback/index.vue') + }, + { + path: '/login', + component: () => import('@/views/login.vue'), + hidden: true + }, + { + path: '/register', + component: () => import('@/views/register.vue'), + hidden: true + }, + { + path: '/:pathMatch(.*)*', + component: () => import('@/views/error/404.vue'), + hidden: true + }, + { + path: '/401', + component: () => import('@/views/error/401.vue'), + hidden: true + }, + { + path: '', + component: Layout, + redirect: '/index', + children: [ + { + path: '/index', + component: () => import('@/views/index.vue'), + name: 'Index', + meta: { title: '首页', icon: 'dashboard', affix: true } + } + ] + }, + { + path: '/user', + component: Layout, + hidden: true, + redirect: 'noredirect', + children: [ + { + path: 'profile', + component: () => import('@/views/system/user/profile/index.vue'), + name: 'Profile', + meta: { title: '个人中心', icon: 'user' } + } + ] + } +]; + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes: RouteOption[] = [ + { + path: '/system/user-auth', + component: Layout, + hidden: true, + permissions: ['system:user:edit'], + children: [ + { + path: 'role/:userId(\\d+)', + component: () => import('@/views/system/user/authRole.vue'), + name: 'AuthRole', + meta: { title: '分配角色', activeMenu: '/system/user', icon: '' } + } + ] + }, + { + path: '/system/role-auth', + component: Layout, + hidden: true, + permissions: ['system:role:edit'], + children: [ + { + path: 'user/:roleId(\\d+)', + component: () => import('@/views/system/role/authUser.vue'), + name: 'AuthUser', + meta: { title: '分配用户', activeMenu: '/system/role', icon: '' } + } + ] + }, + { + path: '/system/dict-data', + component: Layout, + hidden: true, + permissions: ['system:dict:list'], + children: [ + { + path: 'index/:dictId(\\d+)', + component: () => import('@/views/system/dict/data.vue'), + name: 'Data', + meta: { title: '字典数据', activeMenu: '/system/dict', icon: '' } + } + ] + }, + { + path: '/system/oss-config', + component: Layout, + hidden: true, + permissions: ['system:ossConfig:list'], + children: [ + { + path: 'index', + component: () => import('@/views/system/oss/config.vue'), + name: 'OssConfig', + meta: { title: '配置管理', activeMenu: '/system/oss', icon: '' } + } + ] + }, + { + path: '/tool/gen-edit', + component: Layout, + hidden: true, + permissions: ['tool:gen:edit'], + children: [ + { + path: 'index/:tableId(\\d+)', + component: () => import('@/views/tool/gen/editTable.vue'), + name: 'GenEdit', + meta: { title: '修改生成配置', activeMenu: '/tool/gen', icon: '' } + } + ] + } +]; + +/** + * 创建路由 + */ +const router = createRouter({ + history: createWebHistory(import.meta.env.VITE_APP_CONTEXT_PATH), + routes: constantRoutes, + // 刷新时,滚动条位置还原 + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition; + } else { + return { top: 0 }; + } + } +}); + +export default router; diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..9cebde7 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,55 @@ +const setting: DefaultSettings = { + /** + * 网页标题 + */ + title: import.meta.env.VITE_APP_TITLE, + + theme: '#409EFF', + + /** + * 侧边栏主题 深色主题theme-dark,浅色主题theme-light + */ + sideTheme: 'theme-dark', + /** + * 是否系统布局配置 + */ + showSettings: true, + + /** + * 是否显示顶部导航 + */ + topNav: false, + + /** + * 是否显示 tagsView + */ + tagsView: true, + + /** + * 是否固定头部 + */ + fixedHeader: false, + + /** + * 是否显示logo + */ + sidebarLogo: true, + + /** + * 是否显示动态标题 + */ + dynamicTitle: false, + + /** + * @type {string | array} 'production' | ['production', 'development'] + * @description Need show err logs component. + * The default is only used in the production env + * If you want to also use it in dev, you can pass ['production', 'development'] + */ + errorLog: 'production', + + animationEnable: false, + + dark: false +}; +export default setting; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..069d54e --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,3 @@ +const store = createPinia(); + +export default store; diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts new file mode 100644 index 0000000..90866c1 --- /dev/null +++ b/src/store/modules/app.ts @@ -0,0 +1,72 @@ +import zhCN from 'element-plus/es/locale/lang/zh-cn'; +import enUS from 'element-plus/es/locale/lang/en'; + +export const useAppStore = defineStore('app', () => { + const sidebarStatus = useStorage('sidebarStatus', '1'); + const sidebar = reactive({ + opened: sidebarStatus.value ? !!+sidebarStatus.value : true, + withoutAnimation: false, + hide: false + }); + const device = ref('desktop'); + const size = useStorage('size', 'default'); + + // 语言 + const language = useStorage('language', 'zh_CN'); + const languageObj: any = { + en_US: enUS, + zh_CN: zhCN + }; + const locale = computed(() => { + return languageObj[language.value]; + }); + + const toggleSideBar = (withoutAnimation: boolean) => { + if (sidebar.hide) { + return false; + } + + sidebar.opened = !sidebar.opened; + sidebar.withoutAnimation = withoutAnimation; + if (sidebar.opened) { + sidebarStatus.value = '1'; + } else { + sidebarStatus.value = '0'; + } + }; + + const closeSideBar = ({ withoutAnimation }: any): void => { + sidebarStatus.value = '0'; + sidebar.opened = false; + sidebar.withoutAnimation = withoutAnimation; + }; + const toggleDevice = (d: string): void => { + device.value = d; + }; + const setSize = (s: string): void => { + size.value = s; + }; + const toggleSideBarHide = (status: boolean): void => { + sidebar.hide = status; + }; + + const changeLanguage = (val: string): void => { + language.value = val; + }; + + return { + device, + sidebar, + language, + locale, + size, + changeLanguage, + toggleSideBar, + closeSideBar, + toggleDevice, + setSize, + toggleSideBarHide + }; +}); + +export default useAppStore; diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts new file mode 100644 index 0000000..2f937b9 --- /dev/null +++ b/src/store/modules/dict.ts @@ -0,0 +1,78 @@ +export const useDictStore = defineStore('dict', () => { + const dict = ref< + Array<{ + key: string; + value: DictDataOption[]; + }> + >([]); + + /** + * 获取字典 + * @param _key 字典key + */ + const getDict = (_key: string): DictDataOption[] | null => { + if (_key == null && _key == '') { + return null; + } + try { + for (let i = 0; i < dict.value.length; i++) { + if (dict.value[i].key == _key) { + return dict.value[i].value; + } + } + } catch (e) { + return null; + } + return null; + }; + + /** + * 设置字典 + * @param _key 字典key + * @param _value 字典value + */ + const setDict = (_key: string, _value: DictDataOption[]) => { + if (_key !== null && _key !== '') { + dict.value.push({ + key: _key, + value: _value + }); + } + }; + + /** + * 删除字典 + * @param _key + */ + const removeDict = (_key: string): boolean => { + let bln = false; + try { + for (let i = 0; i < dict.value.length; i++) { + if (dict.value[i].key == _key) { + dict.value.splice(i, 1); + return true; + } + } + } catch (e) { + bln = false; + } + return bln; + }; + + /** + * 清空字典 + */ + const cleanDict = (): void => { + dict.value = []; + }; + + return { + dict, + getDict, + setDict, + removeDict, + cleanDict + }; +}); + +export default useDictStore; diff --git a/src/store/modules/notice.ts b/src/store/modules/notice.ts new file mode 100644 index 0000000..f3f8e5a --- /dev/null +++ b/src/store/modules/notice.ts @@ -0,0 +1,42 @@ +import { defineStore } from 'pinia'; + +interface NoticeItem { + title?: string; + read: boolean; + message: any; + time: string; +} + +export const useNoticeStore = defineStore('notice', () => { + const state = reactive({ + notices: [] as NoticeItem[] + }); + + const addNotice = (notice: NoticeItem) => { + state.notices.push(notice); + }; + + const removeNotice = (notice: NoticeItem) => { + state.notices.splice(state.notices.indexOf(notice), 1); + }; + + //实现全部已读 + const readAll = () => { + state.notices.forEach((item) => { + item.read = true; + }); + }; + + const clearNotice = () => { + state.notices = []; + }; + return { + state, + addNotice, + removeNotice, + readAll, + clearNotice + }; +}); + +export default useNoticeStore; diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts new file mode 100644 index 0000000..ed64e46 --- /dev/null +++ b/src/store/modules/permission.ts @@ -0,0 +1,148 @@ +import { defineStore } from 'pinia'; +import router, { constantRoutes, dynamicRoutes } from '@/router'; +import store from '@/store'; +import { getRouters } from '@/api/menu'; +import Layout from '@/layout/index.vue'; +import ParentView from '@/components/ParentView/index.vue'; +import InnerLink from '@/layout/components/InnerLink/index.vue'; +import auth from '@/plugins/auth'; +import { RouteOption } from 'vue-router'; +// 匹配views里面所有的.vue文件 +const modules = import.meta.glob('./../../views/**/*.vue'); + +export const usePermissionStore = defineStore('permission', () => { + const routes = ref([]); + const addRoutes = ref([]); + const defaultRoutes = ref([]); + const topbarRouters = ref([]); + const sidebarRouters = ref([]); + + const setRoutes = (newRoutes: RouteOption[]): void => { + addRoutes.value = newRoutes; + routes.value = constantRoutes.concat(newRoutes); + }; + const setDefaultRoutes = (routes: RouteOption[]): void => { + defaultRoutes.value = constantRoutes.concat(routes); + }; + const setTopbarRoutes = (routes: RouteOption[]): void => { + topbarRouters.value = routes; + }; + const setSidebarRouters = (routes: RouteOption[]): void => { + sidebarRouters.value = routes; + }; + const generateRoutes = async (): Promise => { + const res = await getRouters(); + const { data } = res; + const sdata = JSON.parse(JSON.stringify(data)); + const rdata = JSON.parse(JSON.stringify(data)); + const defaultData = JSON.parse(JSON.stringify(data)); + const sidebarRoutes = filterAsyncRouter(sdata); + const rewriteRoutes = filterAsyncRouter(rdata, undefined, true); + const defaultRoutes = filterAsyncRouter(defaultData); + const asyncRoutes = filterDynamicRoutes(dynamicRoutes); + asyncRoutes.forEach((route) => { + router.addRoute(route); + }); + setRoutes(rewriteRoutes); + setSidebarRouters(constantRoutes.concat(sidebarRoutes)); + setDefaultRoutes(sidebarRoutes); + setTopbarRoutes(defaultRoutes); + return new Promise((resolve) => resolve(rewriteRoutes)); + }; + + /** + * 遍历后台传来的路由字符串,转换为组件对象 + * @param asyncRouterMap 后台传来的路由字符串 + * @param lastRouter 上一级路由 + * @param type 是否是重写路由 + */ + const filterAsyncRouter = (asyncRouterMap: RouteOption[], lastRouter?: RouteOption, type = false): RouteOption[] => { + return asyncRouterMap.filter((route) => { + if (type && route.children) { + route.children = filterChildren(route.children, undefined); + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === 'Layout') { + route.component = Layout; + } else if (route.component === 'ParentView') { + route.component = ParentView; + } else if (route.component === 'InnerLink') { + route.component = InnerLink; + } else { + route.component = loadView(route.component); + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type); + } else { + delete route.children; + delete route.redirect; + } + return true; + }); + }; + const filterChildren = (childrenMap: RouteOption[], lastRouter?: RouteOption): RouteOption[] => { + let children: RouteOption[] = []; + childrenMap.forEach((el) => { + if (el.children && el.children.length) { + if (el.component === 'ParentView' && !lastRouter) { + el.children.forEach((c) => { + c.path = el.path + '/' + c.path; + if (c.children && c.children.length) { + children = children.concat(filterChildren(c.children, c)); + return; + } + children.push(c); + }); + return; + } + } + if (lastRouter) { + el.path = lastRouter.path + '/' + el.path; + if (el.children && el.children.length) { + children = children.concat(filterChildren(el.children, el)) + return + } + } + children = children.concat(el); + }); + return children; + }; + return { routes, setRoutes, generateRoutes, setSidebarRouters, topbarRouters, sidebarRouters, defaultRoutes }; +}); + +// 动态路由遍历,验证是否具备权限 +export const filterDynamicRoutes = (routes: RouteOption[]) => { + const res: RouteOption[] = []; + routes.forEach((route) => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route); + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route); + } + } + }); + return res; +}; + +export const loadView = (view: any) => { + let res; + for (const path in modules) { + const dir = path.split('views/')[1].split('.vue')[0]; + if (dir === view) { + res = () => modules[path](); + } + } + return res; +}; + +// 非setup +export const usePermissionStoreHook = () => { + return usePermissionStore(store); +}; + +export default usePermissionStore; diff --git a/src/store/modules/settings.ts b/src/store/modules/settings.ts new file mode 100644 index 0000000..fcf595c --- /dev/null +++ b/src/store/modules/settings.ts @@ -0,0 +1,54 @@ +import { defineStore } from 'pinia'; +import defaultSettings from '@/settings'; +import { SettingTypeEnum } from '@/enums/SettingTypeEnum'; +import { useDynamicTitle } from '@/utils/dynamicTitle'; +import { Ref } from 'vue'; + +export const useSettingsStore = defineStore('setting', () => { + const storageSetting = JSON.parse(localStorage.getItem('layout-setting') || '{}'); + + const prop: { [key: string]: Ref } = { + title: ref(''), + theme: ref(storageSetting.theme || defaultSettings.theme), + sideTheme: ref(storageSetting.sideTheme || defaultSettings.sideTheme), + showSettings: ref(storageSetting.showSettings || defaultSettings.showSettings), + topNav: ref(storageSetting.topNav === undefined ? defaultSettings.topNav : storageSetting.topNav), + tagsView: ref(storageSetting.tagsView === undefined ? defaultSettings.tagsView : storageSetting.tagsView), + fixedHeader: ref(storageSetting.fixedHeader === undefined ? defaultSettings.fixedHeader : storageSetting.fixedHeader), + sidebarLogo: ref(storageSetting.sidebarLogo === undefined ? defaultSettings.sidebarLogo : storageSetting.sidebarLogo), + dynamicTitle: ref(storageSetting.dynamicTitle === undefined ? defaultSettings.dynamicTitle : storageSetting.dynamicTitle), + animationEnable: ref(storageSetting.animationEnable === undefined ? defaultSettings.animationEnable : storageSetting.animationEnable), + dark: ref(storageSetting.dark || defaultSettings.dark) + }; + + const { title, theme, sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle, animationEnable, dark } = prop; + + // actions + const changeSetting = (param: { key: SettingTypeEnum; value: any }) => { + const { key, value } = param; + if (key in prop) { + prop[key].value = value; + } + }; + const setTitle = (value: string) => { + title.value = value; + useDynamicTitle(); + }; + return { + title, + theme, + sideTheme, + showSettings, + topNav, + tagsView, + fixedHeader, + sidebarLogo, + dynamicTitle, + animationEnable, + dark, + changeSetting, + setTitle + }; +}); + +export default useSettingsStore; diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts new file mode 100644 index 0000000..1c1c9a2 --- /dev/null +++ b/src/store/modules/tagsView.ts @@ -0,0 +1,209 @@ +import { TagView, RouteRecordNormalized } from 'vue-router'; + +export const useTagsViewStore = defineStore('tagsView', () => { + const visitedViews = ref([]); + const cachedViews = ref([]); + const iframeViews = ref([]); + + const addView = (view: TagView) => { + addVisitedView(view); + addCachedView(view); + }; + + const addIframeView = (view: TagView): void => { + if (iframeViews.value.some((v) => v.path === view.path)) return; + iframeViews.value.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ); + }; + const delIframeView = (view: TagView): Promise => { + return new Promise((resolve) => { + iframeViews.value = iframeViews.value.filter((item) => item.path !== view.path); + resolve([...iframeViews.value]); + }); + }; + const addVisitedView = (view: TagView): void => { + if (visitedViews.value.some((v) => v.path === view.path)) return; + visitedViews.value.push( + Object.assign({}, view, { + title: view.meta?.title || 'no-name' + }) + ); + }; + const delView = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => { + return new Promise((resolve) => { + delVisitedView(view); + if (!isDynamicRoute(view)) { + delCachedView(view); + } + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }); + }); + }; + + const delVisitedView = (view: TagView): Promise => { + 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]); + }); + }; + const delCachedView = (view?: TagView): Promise => { + let viewName = ''; + if (view) { + viewName = view.name as string; + } + return new Promise((resolve) => { + const index = cachedViews.value.indexOf(viewName); + index > -1 && cachedViews.value.splice(index, 1); + resolve([...cachedViews.value]); + }); + }; + const delOthersViews = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => { + return new Promise((resolve) => { + delOthersVisitedViews(view); + delOthersCachedViews(view); + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }); + }); + }; + + const delOthersVisitedViews = (view: TagView): Promise => { + return new Promise((resolve) => { + visitedViews.value = visitedViews.value.filter((v) => { + return v.meta?.affix || v.path === view.path; + }); + resolve([...visitedViews.value]); + }); + }; + const delOthersCachedViews = (view: TagView): Promise => { + 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 { + cachedViews.value = []; + } + resolve([...cachedViews.value]); + }); + }; + + const delAllViews = (): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => { + return new Promise((resolve) => { + delAllVisitedViews(); + delAllCachedViews(); + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }); + }); + }; + const delAllVisitedViews = (): Promise => { + return new Promise((resolve) => { + visitedViews.value = visitedViews.value.filter((tag) => tag.meta?.affix); + resolve([...visitedViews.value]); + }); + }; + + const delAllCachedViews = (): Promise => { + return new Promise((resolve) => { + cachedViews.value = []; + resolve([...cachedViews.value]); + }); + }; + + const updateVisitedView = (view: TagView): void => { + for (let v of visitedViews.value) { + if (v.path === view.path) { + v = Object.assign(v, view); + break; + } + } + }; + const delRightTags = (view: TagView): Promise => { + return new Promise((resolve) => { + const index = visitedViews.value.findIndex((v) => v.path === view.path); + if (index === -1) { + return; + } + visitedViews.value = visitedViews.value.filter((item, idx) => { + if (idx <= index || (item.meta && item.meta.affix)) { + return true; + } + const i = cachedViews.value.indexOf(item.name as string); + if (i > -1) { + cachedViews.value.splice(i, 1); + } + return false; + }); + resolve([...visitedViews.value]); + }); + }; + const delLeftTags = (view: TagView): Promise => { + return new Promise((resolve) => { + const index = visitedViews.value.findIndex((v) => v.path === view.path); + if (index === -1) { + return; + } + visitedViews.value = visitedViews.value.filter((item, idx) => { + if (idx >= index || (item.meta && item.meta.affix)) { + return true; + } + const i = cachedViews.value.indexOf(item.name as string); + if (i > -1) { + cachedViews.value.splice(i, 1); + } + return false; + }); + resolve([...visitedViews.value]); + }); + }; + + const addCachedView = (view: TagView): void => { + const viewName = view.name as string; + if (!viewName) return; + if (cachedViews.value.includes(viewName)) return; + if (!view.meta?.noCache) { + cachedViews.value.push(viewName); + } + }; + + const isDynamicRoute = (view: any): boolean => { + // 检查匹配的路由记录中是否有动态段 + return view.matched.some((m: RouteRecordNormalized) => m.path.includes(':')); + }; + + return { + visitedViews, + cachedViews, + iframeViews, + addVisitedView, + addCachedView, + delVisitedView, + delCachedView, + updateVisitedView, + addView, + delView, + delAllViews, + delAllVisitedViews, + delAllCachedViews, + delOthersViews, + delRightTags, + delLeftTags, + addIframeView, + delIframeView + }; +}); + +export default useTagsViewStore; diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..7c02de0 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,88 @@ +import { to } from 'await-to-js'; +import defAva from '@/assets/images/profile.jpg'; +import store from '@/store'; +import { getToken, removeToken, setToken } from '@/utils/auth'; +import { login as loginApi, logout as logoutApi, getInfo as getUserInfo } from '@/api/login'; +import { LoginData } from '@/api/types'; + +export const useUserStore = defineStore('user', () => { + const token = ref(getToken()); + const name = ref(''); + const nickname = ref(''); + const userId = ref(''); + const avatar = ref(''); + const roles = ref>([]); // 用户角色编码集合 → 判断路由权限 + const permissions = ref>([]); // 用户权限编码集合 → 判断按钮权限 + + /** + * 登录 + * @param userInfo + * @returns + */ + const login = async (userInfo: LoginData): Promise => { + const [err, res] = await to(loginApi(userInfo)); + if (res) { + const data = res.data; + setToken(data.access_token); + token.value = data.access_token; + return Promise.resolve(); + } + return Promise.reject(err); + }; + + // 获取用户信息 + const getInfo = async (): Promise => { + const [err, res] = await to(getUserInfo()); + if (res) { + const data = res.data; + const user = data.user; + const profile = user.avatar == '' || user.avatar == null ? defAva : user.avatar; + + if (data.roles && data.roles.length > 0) { + // 验证返回的roles是否是一个非空数组 + roles.value = data.roles; + permissions.value = data.permissions; + } else { + roles.value = ['ROLE_DEFAULT']; + } + name.value = user.userName; + nickname.value = user.nickName; + avatar.value = profile; + userId.value = user.userId; + return Promise.resolve(); + } + return Promise.reject(err); + }; + + // 注销 + const logout = async (): Promise => { + await logoutApi(); + token.value = ''; + roles.value = []; + permissions.value = []; + removeToken(); + }; + + const setAvatar = (value: string) => { + avatar.value = value; + }; + + return { + userId, + token, + nickname, + avatar, + roles, + permissions, + login, + getInfo, + logout, + setAvatar + }; +}); + +export default useUserStore; +// 非setup +export function useUserStoreHook() { + return useUserStore(store); +} diff --git a/src/types/axios.d.ts b/src/types/axios.d.ts new file mode 100644 index 0000000..36a9f8f --- /dev/null +++ b/src/types/axios.d.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +declare module 'axios' { + export interface AxiosResponse { + code: number; + msg: string; + rows: T; + total: number; + } +} diff --git a/src/types/element.d.ts b/src/types/element.d.ts new file mode 100644 index 0000000..ba7598d --- /dev/null +++ b/src/types/element.d.ts @@ -0,0 +1,34 @@ +import type * as ep from 'element-plus'; +declare global { + declare type ElTagType = '' | 'success' | 'warning' | 'info' | 'danger' | 'default' | 'primary'; + declare type ElFormInstance = InstanceType; + declare type ElTableInstance = InstanceType; + declare type ElTreeInstance = InstanceType; + declare type ElTreeSelectInstance = InstanceType; + declare type ElSelectInstance = InstanceType; + declare type ElUploadInstance = InstanceType; + declare type ElCardInstance = InstanceType; + declare type ElDialogInstance = InstanceType; + declare type ElInputInstance = InstanceType; + declare type ElInputNumberInstance = InstanceType; + declare type ElRadioInstance = InstanceType; + declare type ElRadioGroupInstance = InstanceType; + declare type ElRadioButtonInstance = InstanceType; + declare type ElCheckboxInstance = InstanceType; + declare type ElCheckboxGroupInstance = InstanceType; + declare type ElSwitchInstance = InstanceType; + declare type ElDatePickerInstance = InstanceType; + declare type ElTimePickerInstance = InstanceType; + declare type ElTimeSelectInstance = InstanceType; + declare type ElCascaderInstance = InstanceType; + declare type ElColorPickerInstance = InstanceType; + declare type ElRateInstance = InstanceType; + declare type ElSliderInstance = InstanceType; + declare type ElScrollbarInstance = InstanceType; + + declare type TransferKey = ep.TransferKey; + declare type CheckboxValueType = ep.CheckboxValueType; + declare type ElFormRules = ep.FormRules; + declare type DateModelType = ep.DateModelType; + declare type UploadFile = typeof ep.UploadFile; +} diff --git a/src/types/env.d.ts b/src/types/env.d.ts new file mode 100644 index 0000000..9560307 --- /dev/null +++ b/src/types/env.d.ts @@ -0,0 +1,78 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue'; + const component: DefineComponent<{}, {}, any>; + export default component; +} +declare module '*.avif' { + const src: string; + export default src; +} + +declare module '*.bmp' { + const src: string; + export default src; +} + +declare module '*.gif' { + const src: string; + export default src; +} + +declare module '*.jpg' { + const src: string; + export default src; +} + +declare module '*.jpeg' { + const src: string; + export default src; +} + +declare module '*.png' { + const src: string; + export default src; +} + +declare module '*.webp' { + const src: string; + export default src; +} + +declare module '*.svg' { + const src: string; + export default src; +} + +declare module '*.module.css' { + const classes: { readonly [key: string]: string }; + export default classes; +} + +declare module '*.module.scss' { + const classes: { readonly [key: string]: string }; + export default classes; +} + +declare module '*.module.sass' { + const classes: { readonly [key: string]: string }; + export default classes; +} +// 环境变量 +interface ImportMetaEnv { + VITE_APP_TITLE: string; + VITE_APP_PORT: number; + VITE_APP_BASE_API: string; + VITE_APP_BASE_URL: string; + VITE_APP_CONTEXT_PATH: string; + VITE_APP_MONITRO_ADMIN: string; + VITE_APP_POWERJOB_ADMIN: string; + VITE_APP_ENV: string; + VITE_APP_RSA_PUBLIC_KEY: string; + VITE_APP_RSA_PRIVATE_KEY: string; + VITE_APP_CLIENT_ID: string; + VITE_APP_WEBSOCKET: string; +} +interface ImportMeta { + readonly env: ImportMetaEnv; + // readonly glob: any; +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..32081bf --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,91 @@ +import type { ComponentInternalInstance as ComponentInstance, PropType as VuePropType } from 'vue'; + +declare global { + /** vue Instance */ + declare type ComponentInternalInstance = ComponentInstance; + /**vue */ + declare type PropType = VuePropType; + + /** + * 界面字段隐藏属性 + */ + declare interface FieldOption { + key: number; + label: string; + visible: boolean; + children?: Array; + } + + /** + * 弹窗属性 + */ + declare interface DialogOption { + /** + * 弹窗标题 + */ + title?: string; + /** + * 是否显示 + */ + visible: boolean; + } + + declare interface UploadOption { + /** 设置上传的请求头部 */ + headers: { [key: string]: any }; + + /** 上传的地址 */ + url: string; + } + + /** + * 导入属性 + */ + declare interface ImportOption extends UploadOption { + /** 是否显示弹出层 */ + open: boolean; + /** 弹出层标题 */ + title: string; + /** 是否禁用上传 */ + isUploading: boolean; + + /** 其他参数 */ + [key: string]: any; + } + /** + * 字典数据 数据配置 + */ + declare interface DictDataOption { + label: string; + value: string; + elTagType?: ElTagType; + elTagClass?: string; + } + + declare interface BaseEntity { + createBy?: any; + createDept?: any; + createTime?: string; + updateBy?: any; + updateTime?: any; + } + + /** + * 分页数据 + * T : 表单数据 + * D : 查询参数 + */ + declare interface PageData { + form: T; + queryParams: D; + rules: ElFormRules; + } + /** + * 分页查询参数 + */ + declare interface PageQuery { + pageNum: number; + pageSize: number; + } +} +export {}; diff --git a/src/types/module.d.ts b/src/types/module.d.ts new file mode 100644 index 0000000..987c349 --- /dev/null +++ b/src/types/module.d.ts @@ -0,0 +1,32 @@ +import type modal from '@/plugins/modal'; +import type tab from '@/plugins/tab'; +import type download from '@/plugins/download'; +import type auth from '@/plugins/auth'; +import type cache from '@/plugins/cache'; +import type animate from '@/animate'; +import type { useDict } from '@/utils/dict'; +import type { addDateRange, handleTree, selectDictLabel, selectDictLabels, parseTime } from '@/utils/ruoyi'; +import type { getConfigKey, updateConfigByKey } from '@/api/system/config'; +import type { download as rd } from '@/utils/request'; + +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + // 全局方法声明 + $modal: typeof modal; + $tab: typeof tab; + $download: typeof download; + $auth: typeof auth; + $cache: typeof cache; + animate: typeof animate; + + useDict: typeof useDict; + addDateRange: typeof addDateRange; + download: typeof rd; + handleTree: typeof handleTree; + getConfigKey: typeof getConfigKey; + updateConfigByKey: typeof updateConfigByKey; + selectDictLabel: typeof selectDictLabel; + selectDictLabels: typeof selectDictLabels; + parseTime: typeof parseTime; + } +} diff --git a/src/types/router.d.ts b/src/types/router.d.ts new file mode 100644 index 0000000..9731fa2 --- /dev/null +++ b/src/types/router.d.ts @@ -0,0 +1,36 @@ +import { RouteRecordRaw } from 'vue-router'; + +declare module 'vue-router' { + declare type RouteOption = { + hidden?: boolean; + permissions?: string[]; + roles?: string[]; + component?: any; + children?: RouteOption[]; + alwaysShow?: boolean; + parentPath?: string; + meta?: { + title: string; + icon: string; + }; + query?: string; + } & RouteRecordRaw; + + declare interface _RouteLocationBase { + children?: RouteOption[]; + } + + declare interface RouteLocationOptions { + fullPath?: string; + } + + declare interface TagView extends Partial<_RouteLocationBase> { + title?: string; + meta?: { + link?: string; + title?: string; + affix?: boolean; + noCache?: boolean; + }; + } +} diff --git a/src/types/setting.d.ts b/src/types/setting.d.ts new file mode 100644 index 0000000..1b5f9fd --- /dev/null +++ b/src/types/setting.d.ts @@ -0,0 +1,70 @@ +declare type DefaultSettings = { + /** + * 网页标题 + */ + title?: string; + + /** + * 侧边栏主题 theme-dark | theme-light + */ + sideTheme?: string; + + /** + * 是否显示系统布局设置 + */ + showSettings?: boolean; + + /** + * 是否显示顶部导航 + */ + topNav?: boolean; + + /** + * 是否显示多标签导航 + */ + tagsView?: boolean; + /** + * 是否固定头部 + */ + fixedHeader?: boolean; + /** + * 是否显示侧边栏Logo + */ + sidebarLogo?: boolean; + /** + * 导航栏布局 + */ + layout?: string; + /** + * 主题模式 + */ + theme?: string; + + /** + * 布局大小 + */ + size?: string; + + /** + * 语言 + */ + language?: string; + + /** + * 是否显示动态标题 + */ + dynamicTitle?: boolean; + /** + * 是否启用动画效果 + */ + animationEnable?: boolean; + /** + * 是否启用暗黑模式 + * + * true:暗黑模式 + * false: 明亮模式 + */ + dark?: boolean; + + errorLog?: string; +}; diff --git a/src/types/vform3-builds.d.ts b/src/types/vform3-builds.d.ts new file mode 100644 index 0000000..82573de --- /dev/null +++ b/src/types/vform3-builds.d.ts @@ -0,0 +1,4 @@ +declare module 'vform3-builds' { + const content: any; + export = content; +} diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..db50ac9 --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,9 @@ +const TokenKey = 'Admin-Token'; + +const tokenStorage = useStorage(TokenKey, null); + +export const getToken = () => tokenStorage.value; + +export const setToken = (access_token: string) => (tokenStorage.value = access_token); + +export const removeToken = () => (tokenStorage.value = null); diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts new file mode 100644 index 0000000..8217146 --- /dev/null +++ b/src/utils/crypto.ts @@ -0,0 +1,66 @@ +import CryptoJS from 'crypto-js'; + +/** + * 随机生成32位的字符串 + * @returns {string} + */ +const generateRandomString = () => { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + const charactersLength = characters.length; + for (let i = 0; i < 32; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +}; + +/** + * 随机生成aes 密钥 + * @returns {string} + */ +export const generateAesKey = () => { + return CryptoJS.enc.Utf8.parse(generateRandomString()); +}; + +/** + * 加密base64 + * @returns {string} + */ +export const encryptBase64 = (str: CryptoJS.lib.WordArray) => { + return CryptoJS.enc.Base64.stringify(str); +}; + +/** + * 解密base64 + */ +export const decryptBase64 = (str: string) => { + return CryptoJS.enc.Base64.parse(str); +}; + +/** + * 使用密钥对数据进行加密 + * @param message + * @param aesKey + * @returns {string} + */ +export const encryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => { + const encrypted = CryptoJS.AES.encrypt(message, aesKey, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }); + return encrypted.toString(); +}; + +/** + * 使用密钥对数据进行解密 + * @param message + * @param aesKey + * @returns {string} + */ +export const decryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => { + const decrypted = CryptoJS.AES.decrypt(message, aesKey, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }); + return decrypted.toString(CryptoJS.enc.Utf8); +}; diff --git a/src/utils/dict.ts b/src/utils/dict.ts new file mode 100644 index 0000000..0575072 --- /dev/null +++ b/src/utils/dict.ts @@ -0,0 +1,27 @@ +import { getDicts } from '@/api/system/dict/data'; +import { useDictStore } from '@/store/modules/dict'; +/** + * 获取字典数据 + */ +export const useDict = (...args: string[]): { [key: string]: DictDataOption[] } => { + const res = ref<{ + [key: string]: DictDataOption[]; + }>({}); + return (() => { + args.forEach(async (dictType) => { + res.value[dictType] = []; + const dicts = useDictStore().getDict(dictType); + if (dicts) { + res.value[dictType] = dicts; + } else { + await getDicts(dictType).then((resp) => { + res.value[dictType] = resp.data.map( + (p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }) + ); + useDictStore().setDict(dictType, res.value[dictType]); + }); + } + }); + return res.value; + })(); +}; diff --git a/src/utils/dynamicTitle.ts b/src/utils/dynamicTitle.ts new file mode 100644 index 0000000..8e23ef8 --- /dev/null +++ b/src/utils/dynamicTitle.ts @@ -0,0 +1,14 @@ +import defaultSettings from '@/settings'; +import { useSettingsStore } from '@/store/modules/settings'; + +/** + * 动态修改标题 + */ +export const useDynamicTitle = () => { + const settingsStore = useSettingsStore(); + if (settingsStore.dynamicTitle) { + document.title = settingsStore.title + ' - ' + import.meta.env.VITE_APP_TITLE; + } else { + document.title = defaultSettings.title as string; + } +}; diff --git a/src/utils/errorCode.ts b/src/utils/errorCode.ts new file mode 100644 index 0000000..d85914e --- /dev/null +++ b/src/utils/errorCode.ts @@ -0,0 +1,7 @@ +export const errorCode: any = { + '401': '认证失败,无法访问系统资源', + '403': '当前操作没有权限', + '404': '访问资源不存在', + default: '系统未知错误,请反馈给管理员' +}; +export default errorCode; diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts new file mode 100644 index 0000000..ab77af0 --- /dev/null +++ b/src/utils/i18n.ts @@ -0,0 +1,16 @@ +// translate router.meta.title, be used in breadcrumb sidebar tagsview +import i18n from '@/lang/index'; + +/** + * 获取国际化路由,如果不存在则原生返回 + * @param title 路由名称 + * @returns {string} + */ +export const translateRouteTitle = (title: string): string => { + 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..f474462 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,318 @@ +import { parseTime } from '@/utils/ruoyi'; + +/** + * 表格时间格式化 + */ +export const formatDate = (cellValue: string) => { + if (cellValue == null || cellValue == '') return ''; + const date = new Date(cellValue); + const year = date.getFullYear(); + const month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1; + const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate(); + const hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours(); + const minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes(); + const seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds(); + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds; +}; + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export const formatTime = (time: string, option: string) => { + let t: number; + if (('' + time).length === 10) { + t = parseInt(time) * 1000; + } else { + t = +time; + } + const d: any = new Date(t); + const now = Date.now(); + + const diff = (now - d) / 1000; + + if (diff < 30) { + return '刚刚'; + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前'; + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前'; + } else if (diff < 3600 * 24 * 2) { + return '1天前'; + } + if (option) { + return parseTime(t, option); + } else { + return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'; + } +}; + +/** + * @param {string} url + * @returns {Object} + */ +export const getQueryObject = (url: string) => { + url = url == null ? window.location.href : url; + const search = url.substring(url.lastIndexOf('?') + 1); + const obj: { [key: string]: string } = {}; + const reg = /([^?&=]+)=([^?&=]*)/g; + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1); + let val = decodeURIComponent($2); + val = String(val); + obj[name] = val; + return rs; + }); + return obj; +}; + +/** + * @param {string} input value + * @returns {number} output value + */ +export const byteLength = (str: string) => { + // returns the byte length of an utf8 string + let s = str.length; + for (let i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i); + if (code > 0x7f && code <= 0x7ff) s++; + else if (code > 0x7ff && code <= 0xffff) s += 2; + if (code >= 0xdc00 && code <= 0xdfff) i--; + } + return s; +}; + +/** + * @param {Array} actual + * @returns {Array} + */ +export const cleanArray = (actual: Array) => { + const newArray = []; + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]); + } + } + return newArray; +}; + +/** + * @param {Object} json + * @returns {Array} + */ +export const param = (json: any) => { + if (!json) return ''; + return cleanArray( + Object.keys(json).map((key) => { + if (json[key] === undefined) return ''; + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]); + }) + ).join('&'); +}; + +/** + * @param {string} url + * @returns {Object} + */ +export const param2Obj = (url: string) => { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' '); + if (!search) { + return {}; + } + const obj: any = {}; + const searchArr = search.split('&'); + searchArr.forEach((v) => { + const index = v.indexOf('='); + if (index !== -1) { + const name = v.substring(0, index); + const val = v.substring(index + 1, v.length); + obj[name] = val; + } + }); + return obj; +}; + +/** + * @param {string} val + * @returns {string} + */ +export const html2Text = (val: string) => { + const div = document.createElement('div'); + div.innerHTML = val; + return div.textContent || div.innerText; +}; + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export const objectMerge = (target: any, source: any | any[]) => { + if (typeof target !== 'object') { + target = {}; + } + if (Array.isArray(source)) { + return source.slice(); + } + Object.keys(source).forEach((property) => { + const sourceProperty = source[property]; + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty); + } else { + target[property] = sourceProperty; + } + }); + return target; +}; + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export const toggleClass = (element: HTMLElement, className: string) => { + if (!element || !className) { + return; + } + let classString = element.className; + const nameIndex = classString.indexOf(className); + if (nameIndex === -1) { + classString += '' + className; + } else { + classString = classString.substring(0, nameIndex) + classString.substring(nameIndex + className.length); + } + element.className = classString; +}; + +/** + * @param {string} type + * @returns {Date} + */ +export const getTime = (type: string) => { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90; + } else { + return new Date(new Date().toDateString()); + } +}; + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export const debounce = (func: any, wait: number, immediate: boolean) => { + let timeout: any, args: any, context: any, timestamp: any, result: any; + + const later = function () { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp; + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args); + if (!timeout) context = args = null; + } + } + }; + + return (...args: any) => { + context = this; + timestamp = +new Date(); + const callNow = immediate && !timeout; + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + return result; + }; +}; + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export const deepClone = (source: any) => { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone' as any); + } + const targetObj: any = source.constructor === Array ? [] : {}; + Object.keys(source).forEach((keys) => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]); + } else { + targetObj[keys] = source[keys]; + } + }); + return targetObj; +}; + +/** + * @param {Array} arr + * @returns {Array} + */ +export const uniqueArr = (arr: any) => { + return Array.from(new Set(arr)); +}; + +/** + * @returns {string} + */ +export const createUniqueString = (): string => { + const timestamp = +new Date() + ''; + const num = (1 + Math.random()) * 65536; + const randomNum = parseInt(num + ''); + return (+(randomNum + timestamp)).toString(32); +}; + +/** + * Check if an element has a class + * @param ele + * @param {string} cls + * @returns {boolean} + */ +export const hasClass = (ele: HTMLElement, cls: string): boolean => { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); +}; + +/** + * Add class to element + * @param ele + * @param {string} cls + */ +export const addClass = (ele: HTMLElement, cls: string) => { + if (!hasClass(ele, cls)) ele.className += ' ' + cls; +}; + +/** + * Remove class from element + * @param ele + * @param {string} cls + */ +export const 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 const isExternal = (path: string) => { + return /^(https?:|http?:|mailto:|tel:)/.test(path); +}; diff --git a/src/utils/jsencrypt.ts b/src/utils/jsencrypt.ts new file mode 100644 index 0000000..42de5a0 --- /dev/null +++ b/src/utils/jsencrypt.ts @@ -0,0 +1,21 @@ +import JSEncrypt from 'jsencrypt'; +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY; + +// 前端不建议存放私钥 不建议解密数据 因为都是透明的意义不大 +const privateKey = import.meta.env.VITE_APP_RSA_PRIVATE_KEY; + +// 加密 +export const encrypt = (txt: string) => { + const encryptor = new JSEncrypt(); + encryptor.setPublicKey(publicKey); // 设置公钥 + return encryptor.encrypt(txt); // 对数据进行加密 +}; + +// 解密 +export const decrypt = (txt: string) => { + const encryptor = new JSEncrypt(); + encryptor.setPrivateKey(privateKey); // 设置私钥 + return encryptor.decrypt(txt); // 对数据进行解密 +}; diff --git a/src/utils/permission.ts b/src/utils/permission.ts new file mode 100644 index 0000000..eb3838a --- /dev/null +++ b/src/utils/permission.ts @@ -0,0 +1,51 @@ +import useUserStore from '@/store/modules/user'; + +/** + * 字符权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export const checkPermi = (value: any) => { + if (value && value instanceof Array && value.length > 0) { + const permissions = useUserStore().permissions; + const permissionDatas = value; + const all_permission = '*:*:*'; + + const hasPermission = permissions.some((permission) => { + return all_permission === permission || permissionDatas.includes(permission); + }); + + if (!hasPermission) { + return false; + } + return true; + } else { + console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`); + return false; + } +}; + +/** + * 角色权限校验 + * @param {Array} value 校验值 + * @returns {Boolean} + */ +export const checkRole = (value: any): boolean => { + if (value && value instanceof Array && value.length > 0) { + const roles = useUserStore().roles; + const permissionRoles = value; + const super_admin = 'admin'; + + const hasRole = roles.some((role) => { + return super_admin === role || permissionRoles.includes(role); + }); + + if (!hasRole) { + return false; + } + return true; + } else { + console.error(`need roles! Like checkRole="['admin','editor']"`); + return false; + } +}; diff --git a/src/utils/propTypes.ts b/src/utils/propTypes.ts new file mode 100644 index 0000000..1847040 --- /dev/null +++ b/src/utils/propTypes.ts @@ -0,0 +1,25 @@ +import { CSSProperties } from 'vue'; +import VueTypes, { createTypes, toValidableType, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; + +type PropTypes = VueTypesInterface & { + readonly style: VueTypeValidableDef; +}; + +const propTypes = createTypes({ + func: undefined, + bool: undefined, + string: undefined, + number: undefined, + object: undefined, + integer: undefined +}) as PropTypes; + +export default class ProjectTypes extends VueTypes { + static get style() { + return toValidableType('style', { + type: [String, Object], + default: undefined + }); + } +} +export { propTypes }; diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..a183ca6 --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,198 @@ +import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'; +import { useUserStore } from '@/store/modules/user'; +import { getToken } from '@/utils/auth'; +import { tansParams, blobValidate } from '@/utils/ruoyi'; +import cache from '@/plugins/cache'; +import { HttpStatus } from '@/enums/RespEnum'; +import { errorCode } from '@/utils/errorCode'; +import { LoadingInstance } from 'element-plus/es/components/loading/src/loading'; +import FileSaver from 'file-saver'; +import { getLanguage } from '@/lang'; +import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto'; +import { encrypt, decrypt } from '@/utils/jsencrypt'; + +const encryptHeader = 'encrypt-key'; +let downloadLoadingInstance: LoadingInstance; +// 是否显示重新登录 +export const isRelogin = { show: false }; +export const globalHeaders = () => { + return { + Authorization: 'Bearer ' + getToken(), + clientid: import.meta.env.VITE_APP_CLIENT_ID + }; +}; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'; +axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID; +// 创建 axios 实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 50000 +}); + +// 请求拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + // 对应国际化资源文件后缀 + config.headers['Content-Language'] = getLanguage(); + + const isToken = (config.headers || {}).isToken === false; + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false; + // 是否需要加密 + const isEncrypt = (config.headers || {}).isEncrypt === 'true'; + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + }; + const sessionObj = cache.session.getJSON('sessionObj'); + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj); + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 500; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message); + return Promise.reject(new Error(message)); + } else { + cache.session.setJSON('sessionObj', requestObj); + } + } + } + // 当开启参数加密 + if (isEncrypt && (config.method === 'post' || config.method === 'put')) { + // 生成一个 AES 密钥 + const aesKey = generateAesKey(); + config.headers[encryptHeader] = encrypt(encryptBase64(aesKey)); + config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey); + } + // FormData数据去请求头Content-Type + if (config.data instanceof FormData) { + delete config.headers['Content-Type']; + } + return config; + }, + (error: any) => { + console.log(error); + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + (res: AxiosResponse) => { + // 加密后的 AES 秘钥 + const keyStr = res.headers[encryptHeader]; + // 加密 + if (keyStr != null && keyStr != '') { + const data = res.data; + // 请求体 AES 解密 + const base64Str = decrypt(keyStr); + // base64 解码 得到请求头的 AES 秘钥 + const aesKey = decryptBase64(base64Str.toString()); + // aesKey 解码 data + const decryptData = decryptWithAes(data, aesKey); + // 将结果 (得到的是 JSON 字符串) 转为 JSON + res.data = JSON.parse(decryptData); + } + // 未设置状态码则默认成功状态 + const code = res.data.code || HttpStatus.SUCCESS; + // 获取错误信息 + const msg = errorCode[code] || res.data.msg || errorCode['default']; + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data; + } + if (code === 401) { + // prettier-ignore + if (!isRelogin.show) { + isRelogin.show = true; + ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { + confirmButtonText: '重新登录', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + isRelogin.show = false; + useUserStore().logout().then(() => { + location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index'; + }); + }).catch(() => { + isRelogin.show = false; + }); + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。'); + } else if (code === HttpStatus.SERVER_ERROR) { + console.log(msg); + ElMessage({ message: msg, type: 'error' }); + return Promise.reject(new Error(msg)); + } else if (code === HttpStatus.WARN) { + ElMessage({ message: msg, type: 'warning' }); + return Promise.reject(new Error(msg)); + } else if (code !== HttpStatus.SUCCESS) { + ElNotification.error({ title: msg }); + return Promise.reject('error'); + } else { + return Promise.resolve(res.data); + } + }, + (error: any) => { + let { message } = error; + if (message == 'Network Error') { + message = '后端接口连接异常'; + } else if (message.includes('timeout')) { + message = '系统接口请求超时'; + } else if (message.includes('Request failed with status code')) { + message = '系统接口' + message.substr(message.length - 3) + '异常'; + } + ElMessage({ message: message, type: 'error', duration: 5 * 1000 }); + return Promise.reject(error); + } +); +// 通用下载方法 +export function download(url: string, params: any, fileName: string) { + downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' }); + // prettier-ignore + return service.post(url, params, { + transformRequest: [ + (params: any) => { + return tansParams(params); + } + ], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob' + }).then(async (resp: any) => { + const isLogin = blobValidate(resp); + if (isLogin) { + const blob = new Blob([resp]); + FileSaver.saveAs(blob, fileName); + } else { + const resText = await resp.data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']; + ElMessage.error(errMsg); + } + downloadLoadingInstance.close(); + }).catch((r: any) => { + console.error(r); + ElMessage.error('下载文件出现错误,请联系管理员!'); + downloadLoadingInstance.close(); + }); +} +// 导出 axios 实例 +export default service; diff --git a/src/utils/ruoyi.ts b/src/utils/ruoyi.ts new file mode 100644 index 0000000..a116b52 --- /dev/null +++ b/src/utils/ruoyi.ts @@ -0,0 +1,247 @@ +// 日期格式化 +export function parseTime(time: any, pattern?: string) { + if (arguments.length === 0 || !time) { + return null; + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'; + let date; + if (typeof time === 'object') { + date = time; + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time); + } else if (typeof time === 'string') { + time = time + .replace(new RegExp(/-/gm), '/') + .replace('T', ' ') + .replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000; + } + date = new Date(time); + } + const formatObj: { [key: string]: any } = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + }; + return format.replace(/{(y|m|d|h|i|s|a)+}/g, (result: string, key: string) => { + let value = formatObj[key]; + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value]; + } + if (result.length > 0 && value < 10) { + value = '0' + value; + } + return value || 0; + }); +} + +/** + * 添加日期范围 + * @param params + * @param dateRange + * @param propName + */ +export const addDateRange = (params: any, dateRange: any[], propName?: string) => { + const search = params; + search.params = typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof propName === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; +}; + +// 回显数据字典 +export const selectDictLabel = (datas: any, value: number | string) => { + if (value === undefined) { + return ''; + } + const actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == '' + value) { + actions.push(datas[key].label); + return true; + } + }); + if (actions.length === 0) { + actions.push(value); + } + return actions.join(''); +}; + +// 回显数据字典(字符串数组) +export const selectDictLabels = (datas: any, value: any, separator: any) => { + if (value === undefined || value.length === 0) { + return ''; + } + if (Array.isArray(value)) { + value = value.join(','); + } + const actions: any[] = []; + const currentSeparator = undefined === separator ? ',' : separator; + const temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + let match = false; + Object.keys(datas).some((key) => { + if (datas[key].value == '' + temp[val]) { + actions.push(datas[key].label + currentSeparator); + match = true; + } + }); + if (!match) { + actions.push(temp[val] + currentSeparator); + } + }); + return actions.join('').substring(0, actions.join('').length - 1); +}; + +// 字符串格式化(%s ) +export function sprintf(str: string) { + if (arguments.length !== 0) { + let flag = true, + i = 1; + str = str.replace(/%s/g, function () { + const arg = arguments[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; + } +} + +// 转换字符串,undefined,null等转化为"" +export const parseStrEmpty = (str: any) => { + if (!str || str == 'undefined' || str == 'null') { + return ''; + } + return str; +}; + +// 数据合并 +export const mergeRecursive = (source: any, target: any) => { + for (const p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; + } + } + return source; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export const handleTree = (data: any[], id?: string, parentId?: string, children?: string): T[] => { + const config: { + id: string; + parentId: string; + childrenList: string; + } = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; + + const childrenListMap: any = {}; + const nodeIds: any = {}; + const tree: T[] = []; + + for (const d of data) { + const parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (const d of data) { + const parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + const adaptToChildrenList = (o: any) => { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (const c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + }; + + for (const t of tree) { + adaptToChildrenList(t); + } + + return tree; +}; + +/** + * 参数处理 + * @param {*} params 参数 + */ +export const tansParams = (params: any) => { + let result = ''; + for (const propName of Object.keys(params)) { + const value = params[propName]; + const part = encodeURIComponent(propName) + '='; + if (value !== null && value !== '' && typeof value !== 'undefined') { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') { + const params = propName + '[' + key + ']'; + const subPart = encodeURIComponent(params) + '='; + result += subPart + encodeURIComponent(value[key]) + '&'; + } + } + } else { + result += part + encodeURIComponent(value) + '&'; + } + } + } + return result; +}; + +// 返回项目路径 +export const getNormalPath = (p: string): string => { + if (p.length === 0 || !p || p === 'undefined') { + return p; + } + const res = p.replace('//', '/'); + if (res[res.length - 1] === '/') { + return res.slice(0, res.length - 1); + } + return res; +}; + +// 验证是否为blob格式 +export const blobValidate = (data: any) => { + return data.type !== 'application/json'; +}; diff --git a/src/utils/scroll-to.ts b/src/utils/scroll-to.ts new file mode 100644 index 0000000..c2fa379 --- /dev/null +++ b/src/utils/scroll-to.ts @@ -0,0 +1,65 @@ +const easeInOutQuad = (t: number, b: number, c: number, d: number) => { + t /= d / 2; + if (t < 1) { + return (c / 2) * t * t + b; + } + t--; + return (-c / 2) * (t * (t - 2) - 1) + b; +}; + +// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts +const requestAnimFrame = (function () { + return ( + window.requestAnimationFrame || + (window as any).webkitRequestAnimationFrame || + (window as any).mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + } + ); +})(); + +/** + * Because it's so fucking difficult to detect the scrolling element, just move them all + * @param {number} amount + */ +const move = (amount: number) => { + document.documentElement.scrollTop = amount; + (document.body.parentNode as HTMLElement).scrollTop = amount; + document.body.scrollTop = amount; +}; + +const position = () => { + return document.documentElement.scrollTop || (document.body.parentNode as HTMLElement).scrollTop || document.body.scrollTop; +}; + +/** + * @param {number} to + * @param {number} duration + * @param {Function} callback + */ +export const scrollTo = (to: number, duration: number, callback?: any) => { + const start = position(); + const change = to - start; + const increment = 20; + let currentTime = 0; + duration = typeof duration === 'undefined' ? 500 : duration; + const animateScroll = function () { + // increment the time + currentTime += increment; + // find the value with the quadratic in-out easing function + const val = easeInOutQuad(currentTime, start, change, duration); + // move the document.body + move(val); + // do the animation unless its over + if (currentTime < duration) { + requestAnimFrame(animateScroll); + } else { + if (callback && typeof callback === 'function') { + // the animation is done so lets callback + callback(); + } + } + }; + animateScroll(); +}; diff --git a/src/utils/theme.ts b/src/utils/theme.ts new file mode 100644 index 0000000..3936248 --- /dev/null +++ b/src/utils/theme.ts @@ -0,0 +1,52 @@ +// 处理主题样式 +export const handleThemeStyle = (theme: string) => { + document.documentElement.style.setProperty('--el-color-primary', theme); + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`); + } + for (let i = 1; i <= 9; i++) { + document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`); + } +}; + +// hex颜色转rgb颜色 +export const hexToRgb = (str: string): string[] => { + str = str.replace('#', ''); + const hexs = str.match(/../g); + for (let i = 0; i < 3; i++) { + if (hexs) { + hexs[i] = String(parseInt(hexs[i], 16)); + } + } + return hexs ? hexs : []; +}; + +// rgb颜色转Hex颜色 +export const rgbToHex = (r: string, g: string, b: string) => { + const hexs = [Number(r).toString(16), Number(g).toString(16), Number(b).toString(16)]; + for (let i = 0; i < 3; i++) { + if (hexs[i].length == 1) { + hexs[i] = `0${hexs[i]}`; + } + } + return `#${hexs.join('')}`; +}; + +// 变浅颜色值 +export const getLightColor = (color: string, level: number) => { + const rgb = hexToRgb(color); + for (let i = 0; i < 3; i++) { + const s = (255 - Number(rgb[i])) * level + Number(rgb[i]); + rgb[i] = String(Math.floor(s)); + } + return rgbToHex(rgb[0], rgb[1], rgb[2]); +}; + +// 变深颜色值 +export const getDarkColor = (color: string, level: number) => { + const rgb = hexToRgb(color); + for (let i = 0; i < 3; i++) { + rgb[i] = String(Math.floor(Number(rgb[i]) * (1 - level))); + } + return rgbToHex(rgb[0], rgb[1], rgb[2]); +}; diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..4d57894 --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,92 @@ +/** + * 判断url是否是http或https + * @returns {Boolean} + * @param url + */ +export const isHttp = (url: string): boolean => { + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1; +}; + +/** + * 判断path是否为外链 + * @param {string} path + * @returns {Boolean} + */ +export const isExternal = (path: string) => { + return /^(https?:|mailto:|tel:)/.test(path); +}; + +/** + * @param {string} str + * @returns {Boolean} + */ +export const validUsername = (str: string) => { + const valid_map = ['admin', 'editor']; + return valid_map.indexOf(str.trim()) >= 0; +}; + +/** + * @param {string} url + * @returns {Boolean} + */ +export const validURL = (url: string) => { + const reg = + /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/; + return reg.test(url); +}; + +/** + * @param {string} str + * @returns {Boolean} + */ +export const validLowerCase = (str: string) => { + const reg = /^[a-z]+$/; + return reg.test(str); +}; + +/** + * @param {string} str + * @returns {Boolean} + */ +export const validUpperCase = (str: string) => { + const reg = /^[A-Z]+$/; + return reg.test(str); +}; + +/** + * @param {string} str + * @returns {Boolean} + */ +export const validAlphabets = (str: string) => { + const reg = /^[A-Za-z]+$/; + return reg.test(str); +}; + +/** + * @param {string} email + * @returns {Boolean} + */ +export const validEmail = (email: string) => { + const reg = + /^(([^<>()\]\\.,;:\s@"]+(\.[^<>()\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return reg.test(email); +}; + +/** + * @param {string} str + * @returns {Boolean} + */ +export const isString = (str: any) => { + return typeof str === 'string' || str instanceof String; +}; + +/** + * @param {Array} arg + * @returns {Boolean} + */ +export const isArray = (arg: string | string[]) => { + if (typeof Array.isArray === 'undefined') { + return Object.prototype.toString.call(arg) === '[object Array]'; + } + return Array.isArray(arg); +}; diff --git a/src/utils/websocket.ts b/src/utils/websocket.ts new file mode 100644 index 0000000..a4529f9 --- /dev/null +++ b/src/utils/websocket.ts @@ -0,0 +1,141 @@ +/** + * @module initWebSocket 初始化 + * @module websocketonopen 连接成功 + * @module websocketonerror 连接失败 + * @module websocketclose 断开连接 + * @module resetHeart 重置心跳 + * @module sendSocketHeart 心跳发送 + * @module reconnect 重连 + * @module sendMsg 发送数据 + * @module websocketonmessage 接收数据 + * @module test 测试收到消息传递 + * @description socket 通信 + * @param {any} url socket地址 + * @param {any} websocket websocket 实例 + * @param {any} heartTime 心跳定时器实例 + * @param {number} socketHeart 心跳次数 + * @param {number} HeartTimeOut 心跳超时时间 + * @param {number} socketError 错误次数 + */ + +import { getToken } from '@/utils/auth'; +import useNoticeStore from '@/store/modules/notice'; +import { ElNotification } from "element-plus"; + +const { addNotice } = useNoticeStore(); + +let socketUrl: any = ''; // socket地址 +let websocket: any = null; // websocket 实例 +let heartTime: any = null; // 心跳定时器实例 +let socketHeart = 0 as number; // 心跳次数 +const HeartTimeOut = 10000; // 心跳超时时间 10000 = 10s +let socketError = 0 as number; // 错误次数 + +// 初始化socket +export const initWebSocket = (url: any) => { + if (import.meta.env.VITE_APP_WEBSOCKET === 'false') { + return; + } + socketUrl = url; + // 初始化 websocket + websocket = new WebSocket(url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID); + websocketonopen(); + websocketonmessage(); + websocketonerror(); + websocketclose(); + sendSocketHeart(); + return websocket; +}; + +// socket 连接成功 +export const websocketonopen = () => { + websocket.onopen = function () { + console.log('连接 websocket 成功'); + resetHeart(); + }; +}; + +// socket 连接失败 +export const websocketonerror = () => { + websocket.onerror = function (e: any) { + console.log('连接 websocket 失败', e); + }; +}; + +// socket 断开链接 +export const websocketclose = () => { + websocket.onclose = function (e: any) { + console.log('断开连接', e); + }; +}; + +// socket 重置心跳 +export const resetHeart = () => { + socketHeart = 0; + socketError = 0; + clearInterval(heartTime); + sendSocketHeart(); +}; + +// socket心跳发送 +export const sendSocketHeart = () => { + heartTime = setInterval(() => { + // 如果连接正常则发送心跳 + if (websocket.readyState == 1) { + // if (socketHeart <= 30) { + websocket.send( + JSON.stringify({ + type: 'ping' + }) + ); + socketHeart = socketHeart + 1; + } else { + // 重连 + reconnect(); + } + }, HeartTimeOut); +}; + +// socket重连 +export const reconnect = () => { + if (socketError <= 2) { + clearInterval(heartTime); + initWebSocket(socketUrl); + socketError = socketError + 1; + // eslint-disable-next-line prettier/prettier + console.log('socket重连', socketError); + } else { + // eslint-disable-next-line prettier/prettier + console.log('重试次数已用完'); + clearInterval(heartTime); + } +}; + +// socket 发送数据 +export const sendMsg = (data: any) => { + websocket.send(data); +}; + +// socket 接收数据 +export const websocketonmessage = () => { + websocket.onmessage = function (e: any) { + if (e.data.indexOf('heartbeat') > 0) { + resetHeart(); + } + if (e.data.indexOf('ping') > 0) { + return; + } + addNotice({ + message: e.data, + read: false, + time: new Date().toLocaleString() + }); + ElNotification({ + title: '消息', + message: e.data, + type: 'success', + duration: 3000 + }) + return e.data; + }; +}; diff --git a/src/views/demo/demo/index.vue b/src/views/demo/demo/index.vue new file mode 100644 index 0000000..a7f3bd7 --- /dev/null +++ b/src/views/demo/demo/index.vue @@ -0,0 +1,262 @@ + + + diff --git a/src/views/demo/tree/index.vue b/src/views/demo/tree/index.vue new file mode 100644 index 0000000..d79f817 --- /dev/null +++ b/src/views/demo/tree/index.vue @@ -0,0 +1,271 @@ + + + diff --git a/src/views/error/401.vue b/src/views/error/401.vue new file mode 100644 index 0000000..1d9c76e --- /dev/null +++ b/src/views/error/401.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/views/error/404.vue b/src/views/error/404.vue new file mode 100644 index 0000000..94ed936 --- /dev/null +++ b/src/views/error/404.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/views/index.vue b/src/views/index.vue new file mode 100644 index 0000000..12d764b --- /dev/null +++ b/src/views/index.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/src/views/login.vue b/src/views/login.vue new file mode 100644 index 0000000..e5c73fa --- /dev/null +++ b/src/views/login.vue @@ -0,0 +1,283 @@ + + + + + diff --git a/src/views/monitor/admin/index.vue b/src/views/monitor/admin/index.vue new file mode 100644 index 0000000..04c63b5 --- /dev/null +++ b/src/views/monitor/admin/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/views/monitor/cache/index.vue b/src/views/monitor/cache/index.vue new file mode 100644 index 0000000..8719c94 --- /dev/null +++ b/src/views/monitor/cache/index.vue @@ -0,0 +1,191 @@ + + + diff --git a/src/views/monitor/logininfor/index.vue b/src/views/monitor/logininfor/index.vue new file mode 100644 index 0000000..067790e --- /dev/null +++ b/src/views/monitor/logininfor/index.vue @@ -0,0 +1,204 @@ + + + diff --git a/src/views/monitor/online/index.vue b/src/views/monitor/online/index.vue new file mode 100644 index 0000000..d105cd7 --- /dev/null +++ b/src/views/monitor/online/index.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/views/monitor/operlog/index.vue b/src/views/monitor/operlog/index.vue new file mode 100644 index 0000000..ea68c24 --- /dev/null +++ b/src/views/monitor/operlog/index.vue @@ -0,0 +1,302 @@ + + + diff --git a/src/views/monitor/powerjob/index.vue b/src/views/monitor/powerjob/index.vue new file mode 100644 index 0000000..0319e97 --- /dev/null +++ b/src/views/monitor/powerjob/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/views/redirect/index.vue b/src/views/redirect/index.vue new file mode 100644 index 0000000..15401f8 --- /dev/null +++ b/src/views/redirect/index.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/views/register.vue b/src/views/register.vue new file mode 100644 index 0000000..ad78cf1 --- /dev/null +++ b/src/views/register.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/views/system/client/index.vue b/src/views/system/client/index.vue new file mode 100644 index 0000000..b39b9e5 --- /dev/null +++ b/src/views/system/client/index.vue @@ -0,0 +1,324 @@ + + + diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue new file mode 100644 index 0000000..7e2e161 --- /dev/null +++ b/src/views/system/config/index.vue @@ -0,0 +1,257 @@ + + + diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue new file mode 100644 index 0000000..9797125 --- /dev/null +++ b/src/views/system/dept/index.vue @@ -0,0 +1,309 @@ + + + diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue new file mode 100644 index 0000000..c2ef550 --- /dev/null +++ b/src/views/system/dict/data.vue @@ -0,0 +1,289 @@ + + + diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue new file mode 100644 index 0000000..f3b0051 --- /dev/null +++ b/src/views/system/dict/index.vue @@ -0,0 +1,243 @@ + + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..06cd928 --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,410 @@ + + + diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue new file mode 100644 index 0000000..0c41ce1 --- /dev/null +++ b/src/views/system/notice/index.vue @@ -0,0 +1,245 @@ + + + diff --git a/src/views/system/oss/config.vue b/src/views/system/oss/config.vue new file mode 100644 index 0000000..876347a --- /dev/null +++ b/src/views/system/oss/config.vue @@ -0,0 +1,341 @@ + + + diff --git a/src/views/system/oss/index.vue b/src/views/system/oss/index.vue new file mode 100644 index 0000000..ec11708 --- /dev/null +++ b/src/views/system/oss/index.vue @@ -0,0 +1,335 @@ + + + diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue new file mode 100644 index 0000000..b1cddd3 --- /dev/null +++ b/src/views/system/post/index.vue @@ -0,0 +1,237 @@ + + + diff --git a/src/views/system/role/authUser.vue b/src/views/system/role/authUser.vue new file mode 100644 index 0000000..7ad8a5a --- /dev/null +++ b/src/views/system/role/authUser.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..bb6fc2c --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,502 @@ + + + diff --git a/src/views/system/role/selectUser.vue b/src/views/system/role/selectUser.vue new file mode 100644 index 0000000..e0cd433 --- /dev/null +++ b/src/views/system/role/selectUser.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue new file mode 100644 index 0000000..9a7fdf3 --- /dev/null +++ b/src/views/system/tenant/index.vue @@ -0,0 +1,347 @@ + + + diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue new file mode 100644 index 0000000..5311996 --- /dev/null +++ b/src/views/system/tenantPackage/index.vue @@ -0,0 +1,332 @@ + + + diff --git a/src/views/system/user/authRole.vue b/src/views/system/user/authRole.vue new file mode 100644 index 0000000..e869ba3 --- /dev/null +++ b/src/views/system/user/authRole.vue @@ -0,0 +1,127 @@ + + + diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..100b5a7 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,622 @@ + + + + + diff --git a/src/views/system/user/profile/index.vue b/src/views/system/user/profile/index.vue new file mode 100644 index 0000000..47a6632 --- /dev/null +++ b/src/views/system/user/profile/index.vue @@ -0,0 +1,103 @@ + + + diff --git a/src/views/system/user/profile/resetPwd.vue b/src/views/system/user/profile/resetPwd.vue new file mode 100644 index 0000000..11b00f6 --- /dev/null +++ b/src/views/system/user/profile/resetPwd.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/views/system/user/profile/thirdParty.vue b/src/views/system/user/profile/thirdParty.vue new file mode 100644 index 0000000..4170894 --- /dev/null +++ b/src/views/system/user/profile/thirdParty.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/src/views/system/user/profile/userAvatar.vue b/src/views/system/user/profile/userAvatar.vue new file mode 100644 index 0000000..ce25c11 --- /dev/null +++ b/src/views/system/user/profile/userAvatar.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/src/views/system/user/profile/userInfo.vue b/src/views/system/user/profile/userInfo.vue new file mode 100644 index 0000000..3dd5ff7 --- /dev/null +++ b/src/views/system/user/profile/userInfo.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/views/tool/build/index.vue b/src/views/tool/build/index.vue new file mode 100644 index 0000000..ef0c079 --- /dev/null +++ b/src/views/tool/build/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/tool/gen/basicInfoForm.vue b/src/views/tool/gen/basicInfoForm.vue new file mode 100644 index 0000000..c77d0c9 --- /dev/null +++ b/src/views/tool/gen/basicInfoForm.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/views/tool/gen/editTable.vue b/src/views/tool/gen/editTable.vue new file mode 100644 index 0000000..fdd1eeb --- /dev/null +++ b/src/views/tool/gen/editTable.vue @@ -0,0 +1,187 @@ + + + diff --git a/src/views/tool/gen/genInfoForm.vue b/src/views/tool/gen/genInfoForm.vue new file mode 100644 index 0000000..94486e2 --- /dev/null +++ b/src/views/tool/gen/genInfoForm.vue @@ -0,0 +1,291 @@ + + + diff --git a/src/views/tool/gen/importTable.vue b/src/views/tool/gen/importTable.vue new file mode 100644 index 0000000..1ba79ec --- /dev/null +++ b/src/views/tool/gen/importTable.vue @@ -0,0 +1,123 @@ + + + diff --git a/src/views/tool/gen/index.vue b/src/views/tool/gen/index.vue new file mode 100644 index 0000000..cb684f8 --- /dev/null +++ b/src/views/tool/gen/index.vue @@ -0,0 +1,251 @@ + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3dc32e7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "baseUrl": ".", + "allowJs": true, + "paths": { + "@/*": ["src/*"] + }, + "types": ["vite/client"], + "skipLibCheck": true, + // 允许默认导入 + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts", "src/**/*.vue", "src/types/**/*.d.ts"], + "exclude": ["node_modules", "dist", "**/*.js"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..788aace --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,119 @@ +import { UserConfig, ConfigEnv, loadEnv, defineConfig } from 'vite'; + +import createPlugins from './vite/plugins'; + +import path from 'path'; +export default defineConfig(({ mode, command }: ConfigEnv): UserConfig => { + const env = loadEnv(mode, process.cwd()); + return { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + base: env.VITE_APP_CONTEXT_PATH, + resolve: { + alias: { + '~': path.resolve(__dirname, './'), + '@': path.resolve(__dirname, './src'), + 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js' + }, + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] + }, + // https://cn.vitejs.dev/config/#resolve-extensions + plugins: createPlugins(env, command === 'build'), + server: { + host: '0.0.0.0', + port: Number(env.VITE_APP_PORT), + open: true, + proxy: { + [env.VITE_APP_BASE_API]: { + target: 'http://localhost:8080', + changeOrigin: true, + ws: true, + rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '') + } + } + }, + css: { + preprocessorOptions: { + scss: { + javascriptEnabled: true + } + }, + postcss: { + plugins: [ + { + postcssPlugin: 'internal:charset-removal', + AtRule: { + charset: (atRule) => { + if (atRule.name === 'charset') { + atRule.remove(); + } + } + } + } + ] + } + }, + // 预编译 + optimizeDeps: { + include: [ + 'vue', + 'vue-router', + 'pinia', + 'axios', + '@vueuse/core', + 'path-to-regexp', + 'echarts', + '@wangeditor/editor', + '@wangeditor/editor-for-vue', + 'vue-i18n', + '@vueup/vue-quill', + '@iconify/iconify', + + '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/checkbox/style/css', + 'element-plus/es/components/date-picker/style/css', + 'element-plus/es/components/transfer/style/css', + 'element-plus/es/components/tabs/style/css', + 'element-plus/es/components/image/style/css', + 'element-plus/es/components/tab-pane/style/css' + ] + } + }; +}); diff --git a/vite/plugins/auto-import.ts b/vite/plugins/auto-import.ts new file mode 100644 index 0000000..072ef61 --- /dev/null +++ b/vite/plugins/auto-import.ts @@ -0,0 +1,24 @@ +import AutoImport from 'unplugin-auto-import/vite'; +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; +import IconsResolver from 'unplugin-icons/resolver'; + +export default (path: any) => { + return AutoImport({ + // 自动导入 Vue 相关函数 + imports: ['vue', 'vue-router', '@vueuse/core', 'pinia'], + eslintrc: { + enabled: false, + filepath: './.eslintrc-auto-import.json', + globalsPropValue: true + }, + resolvers: [ + // 自动导入 Element Plus 相关函数ElMessage, ElMessageBox... (带样式) + ElementPlusResolver(), + IconsResolver({ + prefix: 'Icon' + }) + ], + vueTemplate: true, // 是否在 vue 模板中自动导入 + dts: path.resolve(path.resolve(__dirname, '../../src'), 'types', 'auto-imports.d.ts') + }); +}; diff --git a/vite/plugins/components.ts b/vite/plugins/components.ts new file mode 100644 index 0000000..336f5cf --- /dev/null +++ b/vite/plugins/components.ts @@ -0,0 +1,17 @@ +import Components from 'unplugin-vue-components/vite'; +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; +import IconsResolver from 'unplugin-icons/resolver'; + +export default (path: any) => { + return Components({ + resolvers: [ + // 自动导入 Element Plus 组件 + ElementPlusResolver(), + // 自动注册图标组件 + IconsResolver({ + enabledCollections: ['ep'] + }) + ], + dts: path.resolve(path.resolve(__dirname, '../../src'), 'types', 'components.d.ts') + }); +}; diff --git a/vite/plugins/compression.ts b/vite/plugins/compression.ts new file mode 100644 index 0000000..123ff3a --- /dev/null +++ b/vite/plugins/compression.ts @@ -0,0 +1,28 @@ +import compression from 'vite-plugin-compression'; + +export default function createCompression(env: any) { + const { VITE_BUILD_COMPRESS } = env; + const plugin: any[] = []; + if (VITE_BUILD_COMPRESS) { + const compressList = VITE_BUILD_COMPRESS.split(','); + if (compressList.includes('gzip')) { + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + plugin.push( + compression({ + ext: '.gz', + deleteOriginFile: false + }) + ); + } + if (compressList.includes('brotli')) { + plugin.push( + compression({ + ext: '.br', + algorithm: 'brotliCompress', + deleteOriginFile: false + }) + ); + } + } + return plugin; +} diff --git a/vite/plugins/icons.ts b/vite/plugins/icons.ts new file mode 100644 index 0000000..883f230 --- /dev/null +++ b/vite/plugins/icons.ts @@ -0,0 +1,8 @@ +import Icons from 'unplugin-icons/vite'; + +export default () => { + return Icons({ + // 自动安装图标库 + autoInstall: true + }); +}; diff --git a/vite/plugins/index.ts b/vite/plugins/index.ts new file mode 100644 index 0000000..a31412d --- /dev/null +++ b/vite/plugins/index.ts @@ -0,0 +1,22 @@ +import vue from '@vitejs/plugin-vue'; +import createUnoCss from './unocss'; +import createAutoImport from './auto-import'; +import createComponents from './components'; +import createIcons from './icons'; +import createSvgIconsPlugin from './svg-icon'; +import createCompression from './compression'; +import createSetupExtend from './setup-extend'; +import path from 'path'; + +export default (viteEnv: any, isBuild = false): [] => { + const vitePlusgins: any = []; + vitePlusgins.push(vue()); + vitePlusgins.push(createUnoCss()); + vitePlusgins.push(createAutoImport(path)); + vitePlusgins.push(createComponents(path)); + vitePlusgins.push(createCompression(viteEnv)); + vitePlusgins.push(createIcons()); + vitePlusgins.push(createSvgIconsPlugin(path, isBuild)); + vitePlusgins.push(createSetupExtend()); + return vitePlusgins; +}; diff --git a/vite/plugins/setup-extend.ts b/vite/plugins/setup-extend.ts new file mode 100644 index 0000000..a1dbb5a --- /dev/null +++ b/vite/plugins/setup-extend.ts @@ -0,0 +1,5 @@ +import setupExtend from 'unplugin-vue-setup-extend-plus/vite' + +export default () => { + return setupExtend({}) +}; diff --git a/vite/plugins/svg-icon.ts b/vite/plugins/svg-icon.ts new file mode 100644 index 0000000..70296da --- /dev/null +++ b/vite/plugins/svg-icon.ts @@ -0,0 +1,10 @@ +import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; +export default (path: any, isBuild: boolean) => { + return createSvgIconsPlugin({ + // 指定需要缓存的图标文件夹 + iconDirs: [path.resolve(path.resolve(__dirname, '../../src'), 'assets/icons/svg')], + // 指定symbolId格式 + symbolId: 'icon-[dir]-[name]', + svgoOptions: isBuild + }); +}; diff --git a/vite/plugins/unocss.ts b/vite/plugins/unocss.ts new file mode 100644 index 0000000..76585c7 --- /dev/null +++ b/vite/plugins/unocss.ts @@ -0,0 +1,14 @@ +import UnoCss from 'unocss/vite'; +import { presetUno, presetAttributify, presetIcons } from 'unocss'; + +export default () => { + return UnoCss({ + presets: [presetUno(), presetAttributify(), presetIcons()], + // rules: [['search', {}]], + shortcuts: { + 'panel-title': + 'pb-[5px] font-sans leading-[1.1] font-medium text-base text-[#6379bb] border-b border-b-solid border-[var(--el-border-color-light)] mb-5 mt-0' + }, + hmrTopLevelAwait: false // unocss默认是true,低版本浏览器是不支持的,启动后会报错 + }); +};